Browse Source

Merge pull request #633 from PixiEditor/node-view-models

Node view models
Krzysztof Krysiński 11 months ago
parent
commit
bd9dc4d014
91 changed files with 517 additions and 305 deletions
  1. 2 12
      src/PixiEditor.ChangeableDocument/ChangeInfos/NodeGraph/NodeMetadata.cs
  2. 1 9
      src/PixiEditor.ChangeableDocument/Changeables/Graph/NodeInfoAttribute.cs
  3. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Animable/TimeNode.cs
  4. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/CombineChannelsNode.cs
  5. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/CombineColorNode.cs
  6. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/CombineVecDNode.cs
  7. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/CombineVecINode.cs
  8. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/SeparateChannelsNode.cs
  9. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/SeparateColorNode.cs
  10. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/SeparateVecDNode.cs
  11. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/SeparateVecINode.cs
  12. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CreateImageNode.cs
  13. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/DebugBlendModeNode.cs
  14. 1 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/EllipseNode.cs
  15. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FilterNodes/ApplyFilterNode.cs
  16. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FilterNodes/ColorMatrixFilterNode.cs
  17. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FilterNodes/GrayscaleNode.cs
  18. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FilterNodes/KernelFilterNode.cs
  19. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FolderNode.cs
  20. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ImageLayerNode.cs
  21. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/LerpColorNode.cs
  22. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/MathNode.cs
  23. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/MergeNode.cs
  24. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ModifyImageLeftNode.cs
  25. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ModifyImageRightNode.cs
  26. 0 5
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Node.cs
  27. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/NoiseNode.cs
  28. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/OutputNode.cs
  29. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/SampleImageNode.cs
  30. 4 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/EllipseData.cs
  31. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/DistributePointsNode.cs
  32. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/EllipseNode.cs
  33. 4 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/RasterizeShapeNode.cs
  34. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/RemoveClosePointsNode.cs
  35. 15 1
      src/PixiEditor.ChangeableDocument/Enums/MathNodeMode.cs
  36. 2 0
      src/PixiEditor.Numerics/VecI.cs
  37. 8 1
      src/PixiEditor/Data/Localization/Languages/en.json
  38. 1 42
      src/PixiEditor/Fonts/NodeIcons.cs
  39. 0 10
      src/PixiEditor/Helpers/IFolderHandlerFactory.cs
  40. 0 10
      src/PixiEditor/Helpers/ILayerHandlerFactory.cs
  41. 35 25
      src/PixiEditor/Models/DocumentModels/DocumentUpdater.cs
  42. 7 0
      src/PixiEditor/Models/Events/NodePropertyValueChanged.cs
  43. 0 2
      src/PixiEditor/Models/Handlers/IDocument.cs
  44. 3 0
      src/PixiEditor/Models/Handlers/INodePropertyHandler.cs
  45. 8 1
      src/PixiEditor/Styles/Templates/NodeGraphView.axaml
  46. 1 1
      src/PixiEditor/Styles/Templates/NodePropertyViewTemplate.axaml
  47. 17 23
      src/PixiEditor/ViewModels/Document/DocumentViewModel.cs
  48. 0 21
      src/PixiEditor/ViewModels/Document/FolderHandlerFactory.cs
  49. 0 16
      src/PixiEditor/ViewModels/Document/FolderViewModel.cs
  50. 1 1
      src/PixiEditor/ViewModels/Document/KeyFrameGroupViewModel.cs
  51. 0 21
      src/PixiEditor/ViewModels/Document/LayerHandlerFactory.cs
  52. 7 0
      src/PixiEditor/ViewModels/Document/Nodes/Animable/TimeNodeViewModel.cs
  53. 7 0
      src/PixiEditor/ViewModels/Document/Nodes/CombineSeparate/CombineChannelsNodeViewModel.cs
  54. 7 0
      src/PixiEditor/ViewModels/Document/Nodes/CombineSeparate/CombineColorNodeViewModel.cs
  55. 7 0
      src/PixiEditor/ViewModels/Document/Nodes/CombineSeparate/CombineVecDNodeViewModel.cs
  56. 7 0
      src/PixiEditor/ViewModels/Document/Nodes/CombineSeparate/CombineVecINodeViewModel.cs
  57. 7 0
      src/PixiEditor/ViewModels/Document/Nodes/CombineSeparate/SeparateChannelsNodeViewModel.cs
  58. 7 0
      src/PixiEditor/ViewModels/Document/Nodes/CombineSeparate/SeparateColorNodeViewModel.cs
  59. 7 0
      src/PixiEditor/ViewModels/Document/Nodes/CombineSeparate/SeparateVecDNodeViewModel.cs
  60. 7 0
      src/PixiEditor/ViewModels/Document/Nodes/CombineSeparate/SeparateVecINodeViewModel.cs
  61. 7 0
      src/PixiEditor/ViewModels/Document/Nodes/CreateImageNodeViewModel.cs
  62. 7 0
      src/PixiEditor/ViewModels/Document/Nodes/DebugBlendModeNodeViewModel.cs
  63. 7 0
      src/PixiEditor/ViewModels/Document/Nodes/FilterNodes/ApplyFilterNodeViewModel.cs
  64. 7 0
      src/PixiEditor/ViewModels/Document/Nodes/FilterNodes/ColorMatrixFilterNodeViewModel.cs
  65. 7 0
      src/PixiEditor/ViewModels/Document/Nodes/FilterNodes/GrayscaleNodeViewModel.cs
  66. 7 0
      src/PixiEditor/ViewModels/Document/Nodes/FilterNodes/KernelFilterNodeViewModel.cs
  67. 13 0
      src/PixiEditor/ViewModels/Document/Nodes/FolderNodeViewModel.cs
  68. 6 13
      src/PixiEditor/ViewModels/Document/Nodes/ImageLayerNodeViewModel.cs
  69. 8 0
      src/PixiEditor/ViewModels/Document/Nodes/LerpColorNodeViewModel.cs
  70. 35 0
      src/PixiEditor/ViewModels/Document/Nodes/MathNodeViewModel.cs
  71. 8 0
      src/PixiEditor/ViewModels/Document/Nodes/MergeNodeViewModel.cs
  72. 8 0
      src/PixiEditor/ViewModels/Document/Nodes/ModifyImageLeftNodeViewModel.cs
  73. 8 0
      src/PixiEditor/ViewModels/Document/Nodes/ModifyImageRightNodeViewModel.cs
  74. 8 0
      src/PixiEditor/ViewModels/Document/Nodes/NoiseNodeViewModel.cs
  75. 8 0
      src/PixiEditor/ViewModels/Document/Nodes/OutputNodeViewModel.cs
  76. 8 0
      src/PixiEditor/ViewModels/Document/Nodes/SampleImageNodeViewModel.cs
  77. 7 0
      src/PixiEditor/ViewModels/Document/Nodes/Shapes/DistributePointsNodeViewModel.cs
  78. 7 0
      src/PixiEditor/ViewModels/Document/Nodes/Shapes/EllipseNodeViewModel.cs
  79. 7 0
      src/PixiEditor/ViewModels/Document/Nodes/Shapes/RasterizeShapeNodeViewModel.cs
  80. 7 0
      src/PixiEditor/ViewModels/Document/Nodes/Shapes/RemoveClosePointsNodeViewModel.cs
  81. 7 19
      src/PixiEditor/ViewModels/Document/Nodes/StructureMemberViewModel.cs
  82. 22 2
      src/PixiEditor/ViewModels/Nodes/NodePropertyViewModel.cs
  83. 27 11
      src/PixiEditor/ViewModels/Nodes/NodeTypeInfo.cs
  84. 48 10
      src/PixiEditor/ViewModels/Nodes/NodeViewModel.cs
  85. 12 0
      src/PixiEditor/ViewModels/Nodes/NodeViewModelAttribute.cs
  86. 5 4
      src/PixiEditor/ViewModels/SubViewModels/LayersViewModel.cs
  87. 4 3
      src/PixiEditor/Views/Layers/FolderControl.axaml.cs
  88. 4 3
      src/PixiEditor/Views/Layers/LayerControl.axaml.cs
  89. 3 2
      src/PixiEditor/Views/Layers/LayersManager.axaml
  90. 3 2
      src/PixiEditor/Views/Layers/LayersManager.axaml.cs
  91. 1 1
      src/PixiEditor/Views/Nodes/NodeGraphView.cs

+ 2 - 12
src/PixiEditor.ChangeableDocument/ChangeInfos/NodeGraph/NodeMetadata.cs

@@ -13,10 +13,11 @@ public class NodeMetadata
     public Guid? PairNodeGuid { get; set; }
     public Guid? PairNodeGuid { get; set; }
     public string? ZoneUniqueName { get; private set; }
     public string? ZoneUniqueName { get; private set; }
     
     
-    public string? Category { get; private set; }
+    public Type NodeType { get; private set; }
 
 
     public NodeMetadata(Type type)
     public NodeMetadata(Type type)
     {
     {
+        NodeType = type;
         AddAttributes(type);
         AddAttributes(type);
     }
     }
     
     
@@ -24,20 +25,9 @@ public class NodeMetadata
 
 
     private void AddAttributes(Type type)
     private void AddAttributes(Type type)
     {
     {
-        AddNodeInfoAttribute(type);
         AddPairAttributes(type);
         AddPairAttributes(type);
     }
     }
 
 
-    private void AddNodeInfoAttribute(Type type)
-    {
-        var attribute = type.GetCustomAttribute<NodeInfoAttribute>();
-
-        if (attribute == null)
-            return;
-
-        Category = attribute.Category;
-    }
-
     private void AddPairAttributes(Type type)
     private void AddPairAttributes(Type type)
     {
     {
         var attribute = type.GetCustomAttribute<PairNodeAttribute>();
         var attribute = type.GetCustomAttribute<PairNodeAttribute>();

+ 1 - 9
src/PixiEditor.ChangeableDocument/Changeables/Graph/NodeInfoAttribute.cs

@@ -5,14 +5,7 @@ public class NodeInfoAttribute : Attribute
 {
 {
     public string UniqueName { get; }
     public string UniqueName { get; }
     
     
-    public string DisplayName { get; }
-    
-    public string? PickerName { get; set; }
-    
-    public string? Category { get; set; }
-    public string? Icon { get; set; }
-
-    public NodeInfoAttribute(string uniqueName, string displayName)
+    public NodeInfoAttribute(string uniqueName)
     {
     {
         if (!uniqueName.StartsWith("PixiEditor"))
         if (!uniqueName.StartsWith("PixiEditor"))
         {
         {
@@ -20,6 +13,5 @@ public class NodeInfoAttribute : Attribute
         }
         }
         
         
         UniqueName = uniqueName;
         UniqueName = uniqueName;
-        DisplayName = displayName;
     }
     }
 }
 }

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

@@ -3,7 +3,7 @@ using PixiEditor.DrawingApi.Core;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Animable;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Animable;
 
 
-[NodeInfo("Time", "TIME_NODE", Category = "ANIMATION")]
+[NodeInfo("Time")]
 public class TimeNode : Node
 public class TimeNode : Node
 {
 {
     public OutputProperty<int> ActiveFrame { get; set; }
     public OutputProperty<int> ActiveFrame { get; set; }

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

@@ -6,7 +6,7 @@ using PixiEditor.Numerics;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
 
 
-[NodeInfo("CombineChannels", "COMBINE_CHANNELS_NODE", Category = "IMAGE")]
+[NodeInfo("CombineChannels")]
 public class CombineChannelsNode : Node
 public class CombineChannelsNode : Node
 {
 {
     private readonly Paint _screenPaint = new() { BlendMode = BlendMode.Screen };
     private readonly Paint _screenPaint = new() { BlendMode = BlendMode.Screen };

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

@@ -6,7 +6,7 @@ using PixiEditor.DrawingApi.Core.Shaders.Generation.Expressions;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
 
 
-[NodeInfo("CombineColor", "COMBINE_COLOR_NODE", Category = "COLOR")]
+[NodeInfo("CombineColor")]
 public class CombineColorNode : Node
 public class CombineColorNode : Node
 {
 {
     public FuncOutputProperty<Half4> Color { get; }
     public FuncOutputProperty<Half4> Color { get; }

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

@@ -7,7 +7,7 @@ using PixiEditor.Numerics;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
 
 
-[NodeInfo("CombineVecD", "COMBINE_VECD_NODE", Category = "NUMBERS")]
+[NodeInfo("CombineVecD")]
 public class CombineVecDNode : Node
 public class CombineVecDNode : Node
 {
 {
     public FuncOutputProperty<Float2> Vector { get; }
     public FuncOutputProperty<Float2> Vector { get; }

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

@@ -6,7 +6,7 @@ using PixiEditor.Numerics;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
 
 
-[NodeInfo("CombineVecI", "COMBINE_VECI_NODE", Category = "NUMBERS")]
+[NodeInfo("CombineVecI")]
 public class CombineVecINode : Node
 public class CombineVecINode : Node
 {
 {
     public FuncOutputProperty<Int2> Vector { get; }
     public FuncOutputProperty<Int2> Vector { get; }

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

@@ -5,7 +5,7 @@ using PixiEditor.Numerics;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
 
 
-[NodeInfo("SeparateChannels", "SEPARATE_CHANNELS_NODE", Category = "IMAGE")]
+[NodeInfo("SeparateChannels")]
 public class SeparateChannelsNode : Node
 public class SeparateChannelsNode : Node
 {
 {
     private readonly Paint _paint = new();
     private readonly Paint _paint = new();

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

@@ -6,7 +6,7 @@ using PixiEditor.DrawingApi.Core.Shaders.Generation.Expressions;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
 
 
-[NodeInfo("SeparateColor", "SEPARATE_COLOR_NODE", Category = "COLOR")]
+[NodeInfo("SeparateColor")]
 public class SeparateColorNode : Node
 public class SeparateColorNode : Node
 {
 {
     public FuncInputProperty<Half4> Color { get; }
     public FuncInputProperty<Half4> Color { get; }

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

@@ -6,7 +6,7 @@ using PixiEditor.Numerics;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
 
 
-[NodeInfo("SeparateVecD", "SEPARATE_VECD_NODE", Category = "NUMBERS")]
+[NodeInfo("SeparateVecD")]
 public class SeparateVecDNode : Node
 public class SeparateVecDNode : Node
 {
 {
     public FuncInputProperty<Float2> Vector { get; }
     public FuncInputProperty<Float2> Vector { get; }

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

@@ -5,7 +5,7 @@ using PixiEditor.Numerics;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
 
 
-[NodeInfo("SeparateVecI", "SEPARATE_VECI_NODE", Category = "NUMBERS")]
+[NodeInfo("SeparateVecI")]
 public class SeparateVecINode : Node
 public class SeparateVecINode : Node
 {
 {
     public FuncInputProperty<Int2> Vector { get; }
     public FuncInputProperty<Int2> Vector { get; }

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/EmptyImageNode.cs → src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CreateImageNode.cs

@@ -7,7 +7,7 @@ using PixiEditor.Numerics;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 
-[NodeInfo("CreateImage", "CREATE_IMAGE_NODE", Category = "IMAGE")]
+[NodeInfo("CreateImage")]
 public class CreateImageNode : Node
 public class CreateImageNode : Node
 {
 {
     private Paint _paint = new();
     private Paint _paint = new();

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

@@ -7,7 +7,7 @@ using PixiEditor.Numerics;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 
 // TODO: Add based on debug mode, not debug build.
 // TODO: Add based on debug mode, not debug build.
-[NodeInfo("DebugBlendMode", "Debug Blend Mode")]
+[NodeInfo("DebugBlendMode")]
 public class DebugBlendModeNode : Node
 public class DebugBlendModeNode : Node
 {
 {
     private Paint _paint = new();
     private Paint _paint = new();

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

@@ -0,0 +1 @@
+

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

@@ -5,7 +5,7 @@ using PixiEditor.DrawingApi.Core.Surfaces.PaintImpl;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
 
 
-[NodeInfo("ApplyFilter", "APPLY_FILTER_NODE", Category = "FILTERS")]
+[NodeInfo("ApplyFilter")]
 public class ApplyFilterNode : Node
 public class ApplyFilterNode : Node
 {
 {
     private Paint _paint = new();
     private Paint _paint = new();

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

@@ -3,7 +3,7 @@ using PixiEditor.Numerics;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
 
 
-[NodeInfo("ColorMatrixFilter", "COLOR_MATRIX_TRANSFORM_FILTER_NODE", Category = "FILTERS")]
+[NodeInfo("ColorMatrixFilter")]
 public class ColorMatrixFilterNode : FilterNode
 public class ColorMatrixFilterNode : FilterNode
 {
 {
     public InputProperty<ColorMatrix> Matrix { get; }
     public InputProperty<ColorMatrix> Matrix { get; }

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

@@ -3,7 +3,7 @@ using PixiEditor.Numerics;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
 
 
-[NodeInfo("GrayscaleFilter", "GRAYSCALE_FILTER_NODE", Category = "FILTERS")]
+[NodeInfo("GrayscaleFilter")]
 public class GrayscaleNode : FilterNode
 public class GrayscaleNode : FilterNode
 {
 {
     private static readonly ColorMatrix WeightedMatrix = ColorMatrix.WeightedWavelengthGrayscale + ColorMatrix.UseAlpha;
     private static readonly ColorMatrix WeightedMatrix = ColorMatrix.WeightedWavelengthGrayscale + ColorMatrix.UseAlpha;

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

@@ -4,7 +4,7 @@ using PixiEditor.Numerics;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
 
 
-[NodeInfo("KernelFilter", "KERNEL_FILTER_NODE", Category = "FILTERS")]
+[NodeInfo("KernelFilter")]
 public class KernelFilterNode : FilterNode
 public class KernelFilterNode : FilterNode
 {
 {
     private readonly Paint _paint = new();
     private readonly Paint _paint = new();

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

@@ -7,7 +7,7 @@ using PixiEditor.Numerics;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 
-[NodeInfo("Folder", "FOLDER_NODE", Category = "STRUCTURE")]
+[NodeInfo("Folder")]
 public class FolderNode : StructureNode, IReadOnlyFolderNode
 public class FolderNode : StructureNode, IReadOnlyFolderNode
 {
 {
     public InputProperty<Texture?> Content { get; }
     public InputProperty<Texture?> Content { get; }

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

@@ -10,7 +10,7 @@ using PixiEditor.Numerics;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 
-[NodeInfo("ImageLayer", "IMAGE_LAYER_NODE", Category = "STRUCTURE")]
+[NodeInfo("ImageLayer")]
 public class ImageLayerNode : LayerNode, IReadOnlyImageNode
 public class ImageLayerNode : LayerNode, IReadOnlyImageNode
 {
 {
     public const string ImageFramesKey = "Frames";
     public const string ImageFramesKey = "Frames";

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

@@ -7,7 +7,7 @@ using PixiEditor.Numerics;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 
-[NodeInfo("Lerp", "LERP_NODE", Category = "NUMBERS")]
+[NodeInfo("Lerp")]
 public class LerpColorNode : Node // TODO: ILerpable as inputs? 
 public class LerpColorNode : Node // TODO: ILerpable as inputs? 
 {
 {
     public FuncOutputProperty<Half4> Result { get; } 
     public FuncOutputProperty<Half4> Result { get; } 

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

@@ -8,7 +8,7 @@ using PixiEditor.DrawingApi.Core.Shaders.Generation.Expressions;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 
-[NodeInfo("Math", "MATH_NODE", Category = "NUMBERS")]
+[NodeInfo("Math")]
 public class MathNode : Node
 public class MathNode : Node
 {
 {
     public FuncOutputProperty<Float1> Result { get; }
     public FuncOutputProperty<Float1> Result { get; }

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

@@ -8,7 +8,7 @@ using PixiEditor.Numerics;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 
-[NodeInfo("Merge", "MERGE_NODE", Category = "IMAGE")]
+[NodeInfo("Merge")]
 public class MergeNode : Node, IBackgroundInput
 public class MergeNode : Node, IBackgroundInput
 {
 {
     private Paint _paint = new();
     private Paint _paint = new();

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

@@ -13,7 +13,7 @@ using PixiEditor.Numerics;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 
-[NodeInfo("ModifyImageLeft", "MODIFY_IMAGE_LEFT_NODE", PickerName = "MODIFY_IMAGE_PAIR_NODE", Category = "IMAGE")]
+[NodeInfo("ModifyImageLeft")]
 [PairNode(typeof(ModifyImageRightNode), "ModifyImageZone", true)]
 [PairNode(typeof(ModifyImageRightNode), "ModifyImageZone", true)]
 public class ModifyImageLeftNode : Node, IPairNode
 public class ModifyImageLeftNode : Node, IPairNode
 {
 {

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

@@ -14,7 +14,7 @@ using PixiEditor.Numerics;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 
-[NodeInfo("ModifyImageRight", "MODIFY_IMAGE_RIGHT_NODE", PickerName = "", Category = "IMAGE")]
+[NodeInfo("ModifyImageRight")]
 [PairNode(typeof(ModifyImageLeftNode), "ModifyImageZone")]
 [PairNode(typeof(ModifyImageLeftNode), "ModifyImageZone")]
 public class ModifyImageRightNode : Node, IPairNode, ICustomShaderNode
 public class ModifyImageRightNode : Node, IPairNode, ICustomShaderNode
 {
 {

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

@@ -45,11 +45,6 @@ public abstract class Node : IReadOnlyNode, IDisposable
 
 
     protected virtual bool AffectedByChunkToUpdate { get; }
     protected virtual bool AffectedByChunkToUpdate { get; }
 
 
-    protected Node()
-    {
-        displayName = GetType().GetCustomAttribute<NodeInfoAttribute>().DisplayName;
-    }
-
     IReadOnlyList<IInputProperty> IReadOnlyNode.InputProperties => inputs;
     IReadOnlyList<IInputProperty> IReadOnlyNode.InputProperties => inputs;
     IReadOnlyList<IOutputProperty> IReadOnlyNode.OutputProperties => outputs;
     IReadOnlyList<IOutputProperty> IReadOnlyNode.OutputProperties => outputs;
     IReadOnlyList<IReadOnlyKeyFrameData> IReadOnlyNode.KeyFrames => keyFrames;
     IReadOnlyList<IReadOnlyKeyFrameData> IReadOnlyNode.KeyFrames => keyFrames;

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

@@ -9,7 +9,7 @@ using PixiEditor.Numerics;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 
-[NodeInfo("Noise", "NOISE_NODE", Category = "IMAGE")]
+[NodeInfo("Noise")]
 public class NoiseNode : Node
 public class NoiseNode : Node
 {
 {
     private double previousScale = double.NaN;
     private double previousScale = double.NaN;

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

@@ -5,7 +5,7 @@ using PixiEditor.DrawingApi.Core;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 
-[NodeInfo("Output", "OUTPUT_NODE", PickerName = "")]
+[NodeInfo("Output")]
 public class OutputNode : Node, IBackgroundInput
 public class OutputNode : Node, IBackgroundInput
 {
 {
     public const string InputPropertyName = "Background";
     public const string InputPropertyName = "Background";

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

@@ -8,7 +8,7 @@ using PixiEditor.Numerics;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 
-[NodeInfo("SampleImage", "SAMPLE_IMAGE", Category = "IMAGE")]
+[NodeInfo("SampleImage")]
 public class SampleImageNode : Node
 public class SampleImageNode : Node
 {
 {
     public InputProperty<Texture?> Image { get; }
     public InputProperty<Texture?> Image { get; }

+ 4 - 2
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/EllipseData.cs

@@ -16,8 +16,10 @@ public class EllipseData : ShapeData
     
     
     public override void Rasterize(DrawingSurface drawingSurface)
     public override void Rasterize(DrawingSurface drawingSurface)
     {
     {
-        using ChunkyImage img = new ChunkyImage(new VecI((int)Radius.X * 2, (int)Radius.Y * 2));
-        RectI rect = new RectI((int)Center.X - (int)Radius.X, (int)Center.Y - (int)Radius.Y, (int)Radius.X * 2, (int)Radius.Y * 2);
+        var imageSize = new VecI((int)Radius.X * 2, (int)Radius.Y * 2);
+
+        using ChunkyImage img = new ChunkyImage(imageSize);
+        RectI rect = new RectI(0, 0, (int)Radius.X * 2, (int)Radius.Y * 2);
         
         
         img.EnqueueDrawEllipse(rect, StrokeColor, FillColor, StrokeWidth);
         img.EnqueueDrawEllipse(rect, StrokeColor, FillColor, StrokeWidth);
         img.CommitChanges();
         img.CommitChanges();

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

@@ -5,7 +5,7 @@ using ShapeData = PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.D
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes;
 
 
-[NodeInfo("DistributePoints", "DISTRIBUTE_POINTS", Category = "SHAPE")]
+[NodeInfo("DistributePoints")]
 public class DistributePointsNode : ShapeNode<PointsData>
 public class DistributePointsNode : ShapeNode<PointsData>
 {
 {
     public InputProperty<int> MaxPointCount { get; }
     public InputProperty<int> MaxPointCount { get; }

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

@@ -6,7 +6,7 @@ using ShapeData = PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.D
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes;
 
 
-[NodeInfo("Ellipse", "ELLIPSE_NODE", Category = "SHAPE")]
+[NodeInfo("Ellipse")]
 public class EllipseNode : ShapeNode<EllipseData>
 public class EllipseNode : ShapeNode<EllipseData>
 {
 {
     public InputProperty<VecD> Position { get; }
     public InputProperty<VecD> Position { get; }

+ 4 - 4
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/RasterizeShape.cs → src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/RasterizeShapeNode.cs

@@ -8,15 +8,15 @@ using ShapeData = PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.D
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes;
 
 
-[NodeInfo("RasterizeShape", "RASTERIZE_SHAPE", Category = "SHAPE")]
-public class RasterizeShape : Node
+[NodeInfo("RasterizeShape")]
+public class RasterizeShapeNode : Node
 {
 {
     public OutputProperty<Texture> Image { get; }
     public OutputProperty<Texture> Image { get; }
 
 
     public InputProperty<ShapeData> Data { get; }
     public InputProperty<ShapeData> Data { get; }
 
 
 
 
-    public RasterizeShape()
+    public RasterizeShapeNode()
     {
     {
         Image = CreateOutput<Texture>("Image", "IMAGE", null);
         Image = CreateOutput<Texture>("Image", "IMAGE", null);
         Data = CreateInput<ShapeData>("Points", "SHAPE", null);
         Data = CreateInput<ShapeData>("Points", "SHAPE", null);
@@ -39,5 +39,5 @@ public class RasterizeShape : Node
         return image;
         return image;
     }
     }
 
 
-    public override Node CreateCopy() => new RasterizeShape();
+    public override Node CreateCopy() => new RasterizeShapeNode();
 }
 }

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

@@ -6,7 +6,7 @@ using ShapeData = PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.D
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes;
 
 
-[NodeInfo("RemoveClosePoints", "REMOVE_CLOSE_POINTS", Category = "SHAPE")]
+[NodeInfo("RemoveClosePoints")]
 public class RemoveClosePointsNode : ShapeNode<PointsData>
 public class RemoveClosePointsNode : ShapeNode<PointsData>
 {
 {
     public InputProperty<PointsData> Input { get; }
     public InputProperty<PointsData> Input { get; }

+ 15 - 1
src/PixiEditor.ChangeableDocument/Enums/MathNodeMode.cs

@@ -1,12 +1,26 @@
-namespace PixiEditor.ChangeableDocument.Enums;
+using System.ComponentModel;
+
+namespace PixiEditor.ChangeableDocument.Enums;
 
 
 public enum MathNodeMode
 public enum MathNodeMode
 {
 {
+    [Description("MATH_ADD")]
     Add,
     Add,
+    [Description("MATH_SUBTRACT")]
     Subtract,
     Subtract,
+    [Description("MULTIPLY")]
     Multiply,
     Multiply,
+    [Description("DIVIDE")]
     Divide,
     Divide,
+    [Description("SIN")]
     Sin,
     Sin,
+    [Description("COS")]
     Cos,
     Cos,
+    [Description("TAN")]
     Tan,
     Tan,
 }
 }
+
+public static class MathNodeModeExtensions
+{
+    public static bool UsesYValue(this MathNodeMode mode) => !(mode is >= MathNodeMode.Sin and <= MathNodeMode.Tan);
+}

+ 2 - 0
src/PixiEditor.Numerics/VecI.cs

@@ -86,6 +86,8 @@ public struct VecI : IEquatable<VecI>, IComparable<VecI>
         return new VecI(Math.Clamp(X, rect.Left, rect.Right), Math.Clamp(Y, rect.Top, rect.Bottom));
         return new VecI(Math.Clamp(X, rect.Left, rect.Right), Math.Clamp(Y, rect.Top, rect.Bottom));
     }
     }
 
 
+    public bool HasNegativeComponent() => X < 0 || Y < 0;
+
     public byte[] ToByteArray()
     public byte[] ToByteArray()
     {
     {
         var data = new byte[sizeof(int) * 2];
         var data = new byte[sizeof(int) * 2];

+ 8 - 1
src/PixiEditor/Data/Localization/Languages/en.json

@@ -738,5 +738,12 @@
   "NUMBER": "Number",
   "NUMBER": "Number",
   "ANIMATION": "Animation",
   "ANIMATION": "Animation",
   "SAMPLE_IMAGE": "Sample Image",
   "SAMPLE_IMAGE": "Sample Image",
-  "POSITION": "Position"
+  "POSITION": "Position",
+  "MATH_ADD": "Add",
+  "MATH_SUBTRACT": "Subtract",
+  "MULTIPLY": "Multiply",
+  "DIVIDE": "Divide",
+  "SIN": "Sin",
+  "COS": "Cos",
+  "TAN": "Tan"
 }
 }

+ 1 - 42
src/PixiEditor/Fonts/NodeIcons.cs

@@ -1,42 +1 @@
-using System.Collections.ObjectModel;
-using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
-using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Animable;
-using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
-using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
-using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes;
-
-namespace PixiEditor.Fonts;
-
-public static class NodeIcons
-{
-    public static ReadOnlyDictionary<Type, string> IconMap { get; } = new ReadOnlyDictionary<Type, string>(
-        new Dictionary<Type, string>
-        {
-            { typeof(TimeNode), "\uE900" },
-            { typeof(FolderNode), "\ue901" },
-            { typeof(CreateImageNode), "\ue902" },
-            { typeof(MergeNode), "\ue903" },
-            { typeof(ModifyImageLeftNode), "\ue904" },
-            { typeof(ImageLayerNode), "\ue905" },
-            { typeof(RasterizeShape), "\ue906" },
-            { typeof(SampleImageNode), "\ue907" },
-            { typeof(CombineColorNode), "\ue908" },
-            { typeof(ApplyFilterNode), "\ue909" },
-            { typeof(DistributePointsNode), "\ue90a" },
-            { typeof(LerpColorNode), "\ue90b" },
-            { typeof(NoiseNode), "\ue90c" },
-            { typeof(EllipseNode), "\ue90d" },
-            { typeof(MathNode), "\ue90e" },
-            { typeof(KernelFilterNode), "\ue90f" },
-            { typeof(CombineChannelsNode), "\ue915" },
-            { typeof(ColorMatrixFilterNode), "\ue911" },
-            { typeof(GrayscaleNode), "\ue912" },
-            { typeof(SeparateColorNode), "\ue913" },
-            { typeof(RemoveClosePointsNode), "\ue914" },
-            { typeof(SeparateChannelsNode), "\ue910" },
-            { typeof(CombineVecDNode), "\ue916" },
-            { typeof(CombineVecINode), "\ue917" },
-            { typeof(SeparateVecDNode), "\ue918" },
-            { typeof(SeparateVecINode), "\ue919" }
-        });
-}
+

+ 0 - 10
src/PixiEditor/Helpers/IFolderHandlerFactory.cs

@@ -1,10 +0,0 @@
-using PixiEditor.Models.DocumentModels;
-using PixiEditor.Models.Handlers;
-
-namespace PixiEditor.Helpers;
-
-internal interface IFolderHandlerFactory
-{
-    public IDocument Document { get; }
-    public IFolderHandler CreateFolderHandler(DocumentInternalParts helper, Guid infoGuidValue);
-}

+ 0 - 10
src/PixiEditor/Helpers/ILayerHandlerFactory.cs

@@ -1,10 +0,0 @@
-using PixiEditor.Models.DocumentModels;
-using PixiEditor.Models.Handlers;
-
-namespace PixiEditor.Helpers;
-
-internal interface ILayerHandlerFactory
-{
-    public IDocument Document { get; }
-    public ILayerHandler CreateLayerHandler(DocumentInternalParts helper, Guid infoGuidValue);
-}

+ 35 - 25
src/PixiEditor/Models/DocumentModels/DocumentUpdater.cs

@@ -1,4 +1,5 @@
 using System.Collections.Immutable;
 using System.Collections.Immutable;
+using System.Reflection;
 using ChunkyImageLib;
 using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.DataHolders;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Exceptions;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Exceptions;
@@ -19,6 +20,7 @@ using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Layers;
 using PixiEditor.Numerics;
 using PixiEditor.Numerics;
 using PixiEditor.ViewModels.Document;
 using PixiEditor.ViewModels.Document;
+using PixiEditor.ViewModels.Document.Nodes;
 using PixiEditor.ViewModels.Nodes;
 using PixiEditor.ViewModels.Nodes;
 
 
 namespace PixiEditor.Models.DocumentModels;
 namespace PixiEditor.Models.DocumentModels;
@@ -55,15 +57,7 @@ internal class DocumentUpdater
         switch (arbitraryInfo)
         switch (arbitraryInfo)
         {
         {
             case CreateStructureMember_ChangeInfo info:
             case CreateStructureMember_ChangeInfo info:
-                if (info is CreateLayer_ChangeInfo layerChangeInfo)
-                {
-                    ProcessCreateNode<LayerViewModel>(info);
-                }
-                else if (info is CreateFolder_ChangeInfo folderChangeInfo)
-                {
-                    ProcessCreateNode<FolderViewModel>(info);
-                }
-
+                ProcessCreateNode(info);
                 ProcessCreateStructureMember(info);
                 ProcessCreateStructureMember(info);
                 break;
                 break;
             case DeleteStructureMember_ChangeInfo info:
             case DeleteStructureMember_ChangeInfo info:
@@ -170,7 +164,7 @@ internal class DocumentUpdater
                 ClearSelectedKeyFrames(info);
                 ClearSelectedKeyFrames(info);
                 break;
                 break;
             case CreateNode_ChangeInfo info:
             case CreateNode_ChangeInfo info:
-                ProcessCreateNode<NodeViewModel>(info);
+                ProcessCreateNode(info);
                 break;
                 break;
             case DeleteNode_ChangeInfo info:
             case DeleteNode_ChangeInfo info:
                 ProcessDeleteNode(info);
                 ProcessDeleteNode(info);
@@ -511,28 +505,44 @@ internal class DocumentUpdater
         doc.AnimationHandler.ClearSelectedKeyFrames();
         doc.AnimationHandler.ClearSelectedKeyFrames();
     }
     }
 
 
-    private void ProcessCreateNode<T>(CreateNode_ChangeInfo info) where T : NodeViewModel, new()
+    private void ProcessCreateNode(CreateNode_ChangeInfo info)
     {
     {
-        T node = new T()
-        {
-            InternalName = info.InternalName, Id = info.Id, Document = (DocumentViewModel)doc, Internals = helper
-        };
+        var nodeType = info.Metadata.NodeType;
+        
+        var ns = nodeType.Namespace.Replace("ChangeableDocument.Changeables.Graph.", "ViewModels.Document.");
+        var name = nodeType.Name.Replace("Node", "NodeViewModel");
+        var fullViewModelName = $"{ns}.{name}";
+        var nodeViewModelType = Type.GetType(fullViewModelName);
+
+        if (nodeViewModelType == null)
+            throw new NullReferenceException($"No ViewModel found for {nodeType}. Looking for '{fullViewModelName}'");
+        
+        var viewModel = (NodeViewModel)Activator.CreateInstance(nodeViewModelType);
 
 
-        node.SetName(info.NodeName);
-        node.SetPosition(info.Position);
+        InitializeNodeViewModel(info, viewModel);
+    }
 
 
-        List<INodePropertyHandler> inputs = CreateProperties(info.Inputs, node, true);
-        List<INodePropertyHandler> outputs = CreateProperties(info.Outputs, node, false);
-        node.Inputs.AddRange(inputs);
-        node.Outputs.AddRange(outputs);
-        doc.NodeGraphHandler.AddNode(node);
+    private void InitializeNodeViewModel(CreateNode_ChangeInfo info, NodeViewModel viewModel)
+    {
+        viewModel.Initialize(info.Id, info.InternalName, (DocumentViewModel)doc, helper);
+        
+        viewModel.SetName(info.NodeName);
+        viewModel.SetPosition(info.Position);
+        
+        var inputs = CreateProperties(info.Inputs, viewModel, true);
+        var outputs = CreateProperties(info.Outputs, viewModel, false);
+        viewModel.Inputs.AddRange(inputs);
+        viewModel.Outputs.AddRange(outputs);
+        doc.NodeGraphHandler.AddNode(viewModel);
 
 
-        node.Metadata = info.Metadata;
+        viewModel.Metadata = info.Metadata;
 
 
-        AddZoneIfNeeded(info, node);
+        AddZoneIfNeeded(info, viewModel);
+        
+        viewModel.OnInitialized();
     }
     }
 
 
-    private void AddZoneIfNeeded<T>(CreateNode_ChangeInfo info, T node) where T : NodeViewModel, new()
+    private void AddZoneIfNeeded(CreateNode_ChangeInfo info, NodeViewModel node)
     {
     {
         if (node.Metadata?.PairNodeGuid != null)
         if (node.Metadata?.PairNodeGuid != null)
         {
         {

+ 7 - 0
src/PixiEditor/Models/Events/NodePropertyValueChanged.cs

@@ -0,0 +1,7 @@
+using PixiEditor.Models.Handlers;
+
+namespace PixiEditor.Models.Events;
+
+public delegate void NodePropertyValueChanged(INodePropertyHandler property, NodePropertyValueChangedArgs args);
+
+public record NodePropertyValueChangedArgs(object OldValue, object NewValue);

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

@@ -31,8 +31,6 @@ internal interface IDocument : IHandler
     public bool AllChangesSaved { get; }
     public bool AllChangesSaved { get; }
     public string CoordinatesString { get; set; }
     public string CoordinatesString { get; set; }
     public IReadOnlyCollection<IStructureMemberHandler> SoftSelectedStructureMembers { get; }
     public IReadOnlyCollection<IStructureMemberHandler> SoftSelectedStructureMembers { get; }
-    public ILayerHandlerFactory LayerHandlerFactory { get; }
-    public IFolderHandlerFactory FolderHandlerFactory { get; }
     public ITransformHandler TransformHandler { get; }
     public ITransformHandler TransformHandler { get; }
     public bool Busy { get; set; }
     public bool Busy { get; set; }
     public ILineOverlayHandler LineToolOverlayHandler { get; }
     public ILineOverlayHandler LineToolOverlayHandler { get; }

+ 3 - 0
src/PixiEditor/Models/Handlers/INodePropertyHandler.cs

@@ -1,4 +1,5 @@
 using System.Collections.ObjectModel;
 using System.Collections.ObjectModel;
+using PixiEditor.Models.Events;
 
 
 namespace PixiEditor.Models.Handlers;
 namespace PixiEditor.Models.Handlers;
 
 
@@ -10,5 +11,7 @@ public interface INodePropertyHandler
     public bool IsInput { get; }
     public bool IsInput { get; }
     public INodePropertyHandler? ConnectedOutput { get; set; }
     public INodePropertyHandler? ConnectedOutput { get; set; }
     public ObservableCollection<INodePropertyHandler> ConnectedInputs { get; }
     public ObservableCollection<INodePropertyHandler> ConnectedInputs { get; }
+
+    public event NodePropertyValueChanged ValueChanged;
     public INodeHandler Node { get; set; }
     public INodeHandler Node { get; set; }
 }
 }

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

@@ -87,7 +87,14 @@
                                         InputNodePosition="{Binding InputNode.PositionBindable}"
                                         InputNodePosition="{Binding InputNode.PositionBindable}"
                                         OutputNodePosition="{Binding OutputNode.PositionBindable}"
                                         OutputNodePosition="{Binding OutputNode.PositionBindable}"
                                         InputProperty="{Binding InputProperty}"
                                         InputProperty="{Binding InputProperty}"
-                                        OutputProperty="{Binding OutputProperty}" />
+                                        OutputProperty="{Binding OutputProperty}">
+                                        <nodes:ConnectionView.IsVisible>
+                                            <MultiBinding Converter="{x:Static BoolConverters.And}">
+                                                <Binding Path="InputProperty.IsVisible" />
+                                                <Binding Path="OutputProperty.IsVisible" />
+                                            </MultiBinding>
+                                        </nodes:ConnectionView.IsVisible>
+                                    </nodes:ConnectionView>
                                 </DataTemplate>
                                 </DataTemplate>
                             </ItemsControl.ItemTemplate>
                             </ItemsControl.ItemTemplate>
                         </ItemsControl>
                         </ItemsControl>

+ 1 - 1
src/PixiEditor/Styles/Templates/NodePropertyViewTemplate.axaml

@@ -6,7 +6,7 @@
         <Setter Property="ClipToBounds" Value="False" />
         <Setter Property="ClipToBounds" Value="False" />
         <Setter Property="Template">
         <Setter Property="Template">
             <ControlTemplate>
             <ControlTemplate>
-                <Grid Margin="-5, 2" ColumnDefinitions="15, *, 15" MinHeight="18">
+                <Grid Margin="-5, 2" ColumnDefinitions="15, *, 15" MinHeight="18" IsVisible="{Binding DataContext.IsVisible, RelativeSource={RelativeSource TemplatedParent}}">
                     <properties:NodeSocket Name="PART_InputSocket"
                     <properties:NodeSocket Name="PART_InputSocket"
                                            ClipToBounds="False"
                                            ClipToBounds="False"
                                            Node="{Binding DataContext.Node, RelativeSource={RelativeSource TemplatedParent}}"
                                            Node="{Binding DataContext.Node, RelativeSource={RelativeSource TemplatedParent}}"

+ 17 - 23
src/PixiEditor/ViewModels/Document/DocumentViewModel.cs

@@ -46,6 +46,7 @@ using PixiEditor.Models.Structures;
 using PixiEditor.Models.Tools;
 using PixiEditor.Models.Tools;
 using PixiEditor.Numerics;
 using PixiEditor.Numerics;
 using PixiEditor.Parser.Skia;
 using PixiEditor.Parser.Skia;
+using PixiEditor.ViewModels.Document.Nodes;
 using PixiEditor.ViewModels.Document.TransformOverlays;
 using PixiEditor.ViewModels.Document.TransformOverlays;
 using PixiEditor.Views.Overlays.SymmetryOverlay;
 using PixiEditor.Views.Overlays.SymmetryOverlay;
 using Color = PixiEditor.DrawingApi.Core.ColorsImpl.Color;
 using Color = PixiEditor.DrawingApi.Core.ColorsImpl.Color;
@@ -147,7 +148,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
     private double verticalSymmetryAxisX;
     private double verticalSymmetryAxisX;
     public double VerticalSymmetryAxisXBindable => verticalSymmetryAxisX;
     public double VerticalSymmetryAxisXBindable => verticalSymmetryAxisX;
 
 
-    private readonly HashSet<StructureMemberViewModel> softSelectedStructureMembers = new();
+    private readonly HashSet<IStructureMemberHandler> softSelectedStructureMembers = new();
 
 
     public bool UpdateableChangeActive => Internals.ChangeController.IsChangeActive;
     public bool UpdateableChangeActive => Internals.ChangeController.IsChangeActive;
 
 
@@ -209,8 +210,6 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
     IDocumentOperations IDocument.Operations => Operations;
     IDocumentOperations IDocument.Operations => Operations;
     ITransformHandler IDocument.TransformHandler => TransformViewModel;
     ITransformHandler IDocument.TransformHandler => TransformViewModel;
     ILineOverlayHandler IDocument.LineToolOverlayHandler => LineToolOverlayViewModel;
     ILineOverlayHandler IDocument.LineToolOverlayHandler => LineToolOverlayViewModel;
-    public ILayerHandlerFactory LayerHandlerFactory { get; }
-    public IFolderHandlerFactory FolderHandlerFactory { get; }
     IReferenceLayerHandler IDocument.ReferenceLayerHandler => ReferenceLayerViewModel;
     IReferenceLayerHandler IDocument.ReferenceLayerHandler => ReferenceLayerViewModel;
     IAnimationHandler IDocument.AnimationHandler => AnimationDataViewModel;
     IAnimationHandler IDocument.AnimationHandler => AnimationDataViewModel;
 
 
@@ -224,8 +223,6 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         EventInlet = new DocumentEventsModule(this, Internals);
         EventInlet = new DocumentEventsModule(this, Internals);
         Operations = new DocumentOperationsModule(this, Internals);
         Operations = new DocumentOperationsModule(this, Internals);
 
 
-        LayerHandlerFactory = new LayerHandlerFactory(this);
-        FolderHandlerFactory = new FolderHandlerFactory(this);
         AnimationDataViewModel = new(this, Internals);
         AnimationDataViewModel = new(this, Internals);
 
 
         NodeGraph = new NodeGraphViewModel(this, Internals);
         NodeGraph = new NodeGraphViewModel(this, Internals);
@@ -537,7 +534,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         IStructureMemberHandler? layerToExtractFrom = null)
         IStructureMemberHandler? layerToExtractFrom = null)
     {
     {
         layerToExtractFrom ??= SelectedStructureMember;
         layerToExtractFrom ??= SelectedStructureMember;
-        if (layerToExtractFrom is not LayerViewModel layerVm)
+        if (layerToExtractFrom is not ILayerHandler layerVm)
             return new Error();
             return new Error();
         if (SelectionPathBindable.IsEmpty)
         if (SelectionPathBindable.IsEmpty)
             return new None();
             return new None();
@@ -667,7 +664,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
                         _ => Colors.Transparent);
                         _ => Colors.Transparent);
             }
             }
 
 
-            if (SelectedStructureMember is not LayerViewModel layerVm)
+            if (SelectedStructureMember is not ILayerHandler layerVm)
                 return Colors.Transparent;
                 return Colors.Transparent;
             IReadOnlyStructureNode? maybeMember = Internals.Tracker.Document.FindMember(layerVm.Id);
             IReadOnlyStructureNode? maybeMember = Internals.Tracker.Document.FindMember(layerVm.Id);
             if (maybeMember is not IReadOnlyImageNode layer)
             if (maybeMember is not IReadOnlyImageNode layer)
@@ -714,8 +711,11 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         OnPropertyChanged(nameof(VerticalSymmetryAxisXBindable));
         OnPropertyChanged(nameof(VerticalSymmetryAxisXBindable));
     }
     }
 
 
-    public void SetSelectedMember(IStructureMemberHandler member) =>
-        SetSelectedMember((StructureMemberViewModel)member);
+    public void SetSelectedMember(IStructureMemberHandler member)
+    {
+        SelectedStructureMember = member;
+        OnPropertyChanged(nameof(SelectedStructureMember));
+    }
 
 
     public void SetHorizontalSymmetryAxisY(double horizontalSymmetryAxisY)
     public void SetHorizontalSymmetryAxisY(double horizontalSymmetryAxisY)
     {
     {
@@ -742,25 +742,19 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         OnPropertyChanged(nameof(SelectionPathBindable));
         OnPropertyChanged(nameof(SelectionPathBindable));
     }
     }
 
 
-    public void SetSelectedMember(StructureMemberViewModel? member)
+    public void AddSoftSelectedMember(IStructureMemberHandler member)
     {
     {
-        SelectedStructureMember = member;
-        OnPropertyChanged(nameof(SelectedStructureMember));
+        softSelectedStructureMembers.Add(member);
     }
     }
 
 
     public void RemoveSoftSelectedMember(IStructureMemberHandler member)
     public void RemoveSoftSelectedMember(IStructureMemberHandler member)
     {
     {
         SelectedStructureMember = member;
         SelectedStructureMember = member;
+        softSelectedStructureMembers.Remove(member);
     }
     }
 
 
     public void ClearSoftSelectedMembers() => softSelectedStructureMembers.Clear();
     public void ClearSoftSelectedMembers() => softSelectedStructureMembers.Clear();
 
 
-    public void AddSoftSelectedMember(IStructureMemberHandler member) =>
-        softSelectedStructureMembers.Add((StructureMemberViewModel)member);
-
-    public void RemoveSoftSelectedMember(StructureMemberViewModel member) =>
-        softSelectedStructureMembers.Remove(member);
-
     #endregion
     #endregion
 
 
     /// <summary>
     /// <summary>
@@ -790,12 +784,12 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             var foundMember = StructureHelper.Find(member);
             var foundMember = StructureHelper.Find(member);
             if (foundMember != null)
             if (foundMember != null)
             {
             {
-                if (foundMember is LayerViewModel layer && selectedMembers.Contains(foundMember.Id) &&
+                if (foundMember is ImageLayerNodeViewModel layer && selectedMembers.Contains(foundMember.Id) &&
                     !result.Contains(layer.Id))
                     !result.Contains(layer.Id))
                 {
                 {
                     result.Add(layer.Id);
                     result.Add(layer.Id);
                 }
                 }
-                else if (foundMember is FolderViewModel folder && selectedMembers.Contains(foundMember.Id))
+                else if (foundMember is FolderNodeViewModel folder && selectedMembers.Contains(foundMember.Id))
                 {
                 {
                     if (includeFoldersWithMask && folder.HasMaskBindable && !result.Contains(folder.Id))
                     if (includeFoldersWithMask && folder.HasMaskBindable && !result.Contains(folder.Id))
                         result.Add(folder.Id);
                         result.Add(folder.Id);
@@ -812,16 +806,16 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         OnPropertyChanged(nameof(AllChangesSaved));
         OnPropertyChanged(nameof(AllChangesSaved));
     }
     }
 
 
-    private void ExtractSelectedLayers(FolderViewModel folder, List<Guid> list,
+    private void ExtractSelectedLayers(FolderNodeViewModel folder, List<Guid> list,
         bool includeFoldersWithMask)
         bool includeFoldersWithMask)
     {
     {
         foreach (var member in folder.Children)
         foreach (var member in folder.Children)
         {
         {
-            if (member is LayerViewModel layer && !list.Contains(layer.Id))
+            if (member is ImageLayerNodeViewModel layer && !list.Contains(layer.Id))
             {
             {
                 list.Add(layer.Id);
                 list.Add(layer.Id);
             }
             }
-            else if (member is FolderViewModel childFolder)
+            else if (member is FolderNodeViewModel childFolder)
             {
             {
                 if (includeFoldersWithMask && childFolder.HasMaskBindable && !list.Contains(childFolder.Id))
                 if (includeFoldersWithMask && childFolder.HasMaskBindable && !list.Contains(childFolder.Id))
                     list.Add(childFolder.Id);
                     list.Add(childFolder.Id);

+ 0 - 21
src/PixiEditor/ViewModels/Document/FolderHandlerFactory.cs

@@ -1,21 +0,0 @@
-using PixiEditor.Helpers;
-using PixiEditor.Models.DocumentModels;
-using PixiEditor.Models.Handlers;
-
-namespace PixiEditor.ViewModels.Document;
-
-internal class FolderHandlerFactory : IFolderHandlerFactory
-{
-    public DocumentViewModel Document { get; }
-    IDocument IFolderHandlerFactory.Document => Document;
-
-    public FolderHandlerFactory(DocumentViewModel document)
-    {
-        Document = document;
-    }
-
-    public IFolderHandler CreateFolderHandler(DocumentInternalParts helper, Guid infoGuidValue)
-    {
-        return new FolderViewModel(Document, helper, infoGuidValue);
-    }
-}

+ 0 - 16
src/PixiEditor/ViewModels/Document/FolderViewModel.cs

@@ -1,16 +0,0 @@
-using System.Collections.ObjectModel;
-using PixiEditor.Models.DocumentModels;
-using PixiEditor.Models.Handlers;
-
-namespace PixiEditor.ViewModels.Document;
-#nullable enable
-internal class FolderViewModel : StructureMemberViewModel, IFolderHandler
-{
-    public FolderViewModel()
-    {
-        
-    }
-    
-    public ObservableCollection<IStructureMemberHandler> Children { get; } = new();
-    public FolderViewModel(DocumentViewModel doc, DocumentInternalParts internals, Guid id) : base(doc, internals, id) { }
-}

+ 1 - 1
src/PixiEditor/ViewModels/Document/KeyFrameGroupViewModel.cs

@@ -52,7 +52,7 @@ internal class KeyFrameGroupViewModel : KeyFrameViewModel, IKeyFrameGroupHandler
         Children.CollectionChanged += ChildrenOnCollectionChanged;
         Children.CollectionChanged += ChildrenOnCollectionChanged;
         Document.StructureHelper.Find(LayerGuid).PropertyChanged += (sender, args) =>
         Document.StructureHelper.Find(LayerGuid).PropertyChanged += (sender, args) =>
         {
         {
-            if (args.PropertyName == nameof(StructureMemberViewModel.NodeNameBindable))
+            if (args.PropertyName == nameof(IStructureMemberHandler.NodeNameBindable))
             {
             {
                 OnPropertyChanged(nameof(LayerName));
                 OnPropertyChanged(nameof(LayerName));
             }
             }

+ 0 - 21
src/PixiEditor/ViewModels/Document/LayerHandlerFactory.cs

@@ -1,21 +0,0 @@
-using PixiEditor.Helpers;
-using PixiEditor.Models.DocumentModels;
-using PixiEditor.Models.Handlers;
-
-namespace PixiEditor.ViewModels.Document;
-
-internal class LayerHandlerFactory : ILayerHandlerFactory
-{
-    public DocumentViewModel Document { get; }
-    IDocument ILayerHandlerFactory.Document => Document;
-
-    public LayerHandlerFactory(DocumentViewModel document)
-    {
-        Document = document;
-    }
-
-    public ILayerHandler CreateLayerHandler(DocumentInternalParts helper, Guid infoGuidValue)
-    {
-        return new LayerViewModel(Document, helper, infoGuidValue);
-    }
-}

+ 7 - 0
src/PixiEditor/ViewModels/Document/Nodes/Animable/TimeNodeViewModel.cs

@@ -0,0 +1,7 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Animable;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes.Animable;
+
+[NodeViewModel("TIME_NODE", "ANIMATION", "\uE900")]
+internal class TimeNodeViewModel : NodeViewModel<TimeNode>;

+ 7 - 0
src/PixiEditor/ViewModels/Document/Nodes/CombineSeparate/CombineChannelsNodeViewModel.cs

@@ -0,0 +1,7 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes.CombineSeparate;
+
+[NodeViewModel("COMBINE_CHANNELS_NODE", "IMAGE", "\ue915")]
+internal class CombineChannelsNodeViewModel : NodeViewModel<CombineChannelsNode>;

+ 7 - 0
src/PixiEditor/ViewModels/Document/Nodes/CombineSeparate/CombineColorNodeViewModel.cs

@@ -0,0 +1,7 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes.CombineSeparate;
+
+[NodeViewModel("COMBINE_COLOR_NODE", "COLOR", "\ue908")]
+internal class CombineColorNodeViewModel : NodeViewModel<CombineColorNode>;

+ 7 - 0
src/PixiEditor/ViewModels/Document/Nodes/CombineSeparate/CombineVecDNodeViewModel.cs

@@ -0,0 +1,7 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes.CombineSeparate;
+
+[NodeViewModel("COMBINE_VECD_NODE", "NUMBERS", "\ue916")]
+internal class CombineVecDNodeViewModel : NodeViewModel<CombineVecDNode>;

+ 7 - 0
src/PixiEditor/ViewModels/Document/Nodes/CombineSeparate/CombineVecINodeViewModel.cs

@@ -0,0 +1,7 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes.CombineSeparate;
+
+[NodeViewModel("COMBINE_VECI_NODE", "NUMBERS", "\ue917")]
+internal class CombineVecINodeViewModel : NodeViewModel<CombineVecINode>;

+ 7 - 0
src/PixiEditor/ViewModels/Document/Nodes/CombineSeparate/SeparateChannelsNodeViewModel.cs

@@ -0,0 +1,7 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes.CombineSeparate;
+
+[NodeViewModel("SEPARATE_CHANNELS_NODE", "IMAGE", "\ue910")]
+internal class SeparateChannelsNodeViewModel : NodeViewModel<SeparateChannelsNode>;

+ 7 - 0
src/PixiEditor/ViewModels/Document/Nodes/CombineSeparate/SeparateColorNodeViewModel.cs

@@ -0,0 +1,7 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes.CombineSeparate;
+
+[NodeViewModel("SEPARATE_COLOR_NODE", "COLOR", "\ue913")]
+internal class SeparateColorNodeViewModel : NodeViewModel<SeparateColorNode>;

+ 7 - 0
src/PixiEditor/ViewModels/Document/Nodes/CombineSeparate/SeparateVecDNodeViewModel.cs

@@ -0,0 +1,7 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes.CombineSeparate;
+
+[NodeViewModel("SEPARATE_VECD_NODE", "NUMBERS", "\ue918")]
+internal class SeparateVecDNodeViewModel : NodeViewModel<SeparateVecDNode>;

+ 7 - 0
src/PixiEditor/ViewModels/Document/Nodes/CombineSeparate/SeparateVecINodeViewModel.cs

@@ -0,0 +1,7 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes.CombineSeparate;
+
+[NodeViewModel("SEPARATE_VECI_NODE", "NUMBERS", "\ue917")]
+internal class SeparateVecINodeViewModel : NodeViewModel<SeparateVecINode>;

+ 7 - 0
src/PixiEditor/ViewModels/Document/Nodes/CreateImageNodeViewModel.cs

@@ -0,0 +1,7 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes;
+
+[NodeViewModel("CREATE_IMAGE_NODE", "IMAGE", "\ue902")]
+internal class CreateImageNodeViewModel : NodeViewModel<CreateImageNode>;

+ 7 - 0
src/PixiEditor/ViewModels/Document/Nodes/DebugBlendModeNodeViewModel.cs

@@ -0,0 +1,7 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes;
+
+[NodeViewModel("Debug Blend Mode", "", null)]
+internal class DebugBlendModeNodeViewModel : NodeViewModel<DebugBlendModeNode>;

+ 7 - 0
src/PixiEditor/ViewModels/Document/Nodes/FilterNodes/ApplyFilterNodeViewModel.cs

@@ -0,0 +1,7 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes.FilterNodes;
+
+[NodeViewModel("APPLY_FILTER_NODE", "FILTERS", "\ue909")]
+internal class ApplyFilterNodeViewModel : NodeViewModel<ApplyFilterNode>;

+ 7 - 0
src/PixiEditor/ViewModels/Document/Nodes/FilterNodes/ColorMatrixFilterNodeViewModel.cs

@@ -0,0 +1,7 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes.FilterNodes;
+
+[NodeViewModel("COLOR_MATRIX_TRANSFORM_FILTER_NODE", "FILTERS", "\ue911")]
+internal class ColorMatrixFilterNodeViewModel : NodeViewModel<ColorMatrixFilterNode>;

+ 7 - 0
src/PixiEditor/ViewModels/Document/Nodes/FilterNodes/GrayscaleNodeViewModel.cs

@@ -0,0 +1,7 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes.FilterNodes;
+
+[NodeViewModel("GRAYSCALE_FILTER_NODE", "FILTERS", "\ue912")]
+internal class GrayscaleNodeViewModel : NodeViewModel<GrayscaleNode>;

+ 7 - 0
src/PixiEditor/ViewModels/Document/Nodes/FilterNodes/KernelFilterNodeViewModel.cs

@@ -0,0 +1,7 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes.FilterNodes;
+
+[NodeViewModel("KERNEL_FILTER_NODE", "FILTERS", "\ue90f")]
+internal class KernelFilterNodeViewModel : NodeViewModel<KernelFilterNode>;

+ 13 - 0
src/PixiEditor/ViewModels/Document/Nodes/FolderNodeViewModel.cs

@@ -0,0 +1,13 @@
+using System.Collections.ObjectModel;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.Extensions.Common.Localization;
+using PixiEditor.Models.Handlers;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes;
+
+[NodeViewModel("FOLDER_NODE", "STRUCTURE", "\ue901")]
+internal class FolderNodeViewModel : StructureMemberViewModel<FolderNode>, IFolderHandler
+{
+    public ObservableCollection<IStructureMemberHandler> Children { get; } = new();
+}

+ 6 - 13
src/PixiEditor/ViewModels/Document/LayerViewModel.cs → src/PixiEditor/ViewModels/Document/Nodes/ImageLayerNodeViewModel.cs

@@ -1,16 +1,13 @@
 using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.ChangeableDocument.Actions.Generated;
-using PixiEditor.Models.DocumentModels;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers;
+using PixiEditor.ViewModels.Nodes;
 
 
-namespace PixiEditor.ViewModels.Document;
-#nullable enable
-internal class LayerViewModel : StructureMemberViewModel, ILayerHandler
+namespace PixiEditor.ViewModels.Document.Nodes;
+
+[NodeViewModel("IMAGE_LAYER_NODE", "STRUCTURE", "\ue905")]
+internal class ImageLayerNodeViewModel : StructureMemberViewModel<ImageLayerNode>, ILayerHandler
 {
 {
-    public LayerViewModel()
-    {
-        
-    }
-    
     bool lockTransparency;
     bool lockTransparency;
     public void SetLockTransparency(bool lockTransparency)
     public void SetLockTransparency(bool lockTransparency)
     {
     {
@@ -39,8 +36,4 @@ internal class LayerViewModel : StructureMemberViewModel, ILayerHandler
             OnPropertyChanged(nameof(ShouldDrawOnMask));
             OnPropertyChanged(nameof(ShouldDrawOnMask));
         }
         }
     }
     }
-
-    public LayerViewModel(DocumentViewModel doc, DocumentInternalParts internals, Guid id) : base(doc, internals, id)
-    {
-    }
 }
 }

+ 8 - 0
src/PixiEditor/ViewModels/Document/Nodes/LerpColorNodeViewModel.cs

@@ -0,0 +1,8 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.Extensions.Common.Localization;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes;
+
+[NodeViewModel("LERP_NODE", "NUMBERS", "\ue90b")]
+internal class LerpColorNodeViewModel : NodeViewModel<LerpColorNode>;

+ 35 - 0
src/PixiEditor/ViewModels/Document/Nodes/MathNodeViewModel.cs

@@ -0,0 +1,35 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.Enums;
+using PixiEditor.Extensions.Common.Localization;
+using PixiEditor.Extensions.Helpers;
+using PixiEditor.Models.Events;
+using PixiEditor.Models.Handlers;
+using PixiEditor.ViewModels.Nodes;
+using PixiEditor.ViewModels.Nodes.Properties;
+
+namespace PixiEditor.ViewModels.Document.Nodes;
+
+[NodeViewModel("MATH_NODE", "NUMBERS", "\ue90e")]
+internal class MathNodeViewModel : NodeViewModel<MathNode>
+{
+    private GenericEnumPropertyViewModel Mode { get; set; }
+    
+    private NodePropertyViewModel Y { get; set; }
+    
+    public override void OnInitialized()
+    {
+        Mode = FindInputProperty("Mode") as GenericEnumPropertyViewModel;
+        Y = FindInputProperty("Y");
+        
+        Mode.ValueChanged += ModeChanged;
+    }
+
+    private void ModeChanged(INodePropertyHandler property, NodePropertyValueChangedArgs args)
+    {
+        if (Mode.Value is not MathNodeMode mode)
+            return;
+
+        DisplayName = mode.GetDescription();
+        Y.IsVisible = mode.UsesYValue();
+    }
+}

+ 8 - 0
src/PixiEditor/ViewModels/Document/Nodes/MergeNodeViewModel.cs

@@ -0,0 +1,8 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.Extensions.Common.Localization;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes;
+
+[NodeViewModel("MERGE_NODE", "IMAGE", "\ue903")]
+internal class MergeNodeViewModel : NodeViewModel<MergeNode>;

+ 8 - 0
src/PixiEditor/ViewModels/Document/Nodes/ModifyImageLeftNodeViewModel.cs

@@ -0,0 +1,8 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.Extensions.Common.Localization;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes;
+
+[NodeViewModel("MODIFY_IMAGE_LEFT_NODE", "IMAGE", "\ue904")]
+internal class ModifyImageLeftNodeViewModel : NodeViewModel<ModifyImageLeftNode>;

+ 8 - 0
src/PixiEditor/ViewModels/Document/Nodes/ModifyImageRightNodeViewModel.cs

@@ -0,0 +1,8 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.Extensions.Common.Localization;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes;
+
+[NodeViewModel("MODIFY_IMAGE_RIGHT_NODE", "IMAGE", null)]
+internal class ModifyImageRightNodeViewModel : NodeViewModel<ModifyImageRightNode>;

+ 8 - 0
src/PixiEditor/ViewModels/Document/Nodes/NoiseNodeViewModel.cs

@@ -0,0 +1,8 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.Extensions.Common.Localization;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes;
+
+[NodeViewModel("NOISE_NODE", "IMAGE", "\ue90c")]
+internal class NoiseNodeViewModel : NodeViewModel<NoiseNode>;

+ 8 - 0
src/PixiEditor/ViewModels/Document/Nodes/OutputNodeViewModel.cs

@@ -0,0 +1,8 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.Extensions.Common.Localization;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes;
+
+[NodeViewModel("OUTPUT_NODE", "", null, PickerName = "")]
+internal class OutputNodeViewModel : NodeViewModel<OutputNode>;

+ 8 - 0
src/PixiEditor/ViewModels/Document/Nodes/SampleImageNodeViewModel.cs

@@ -0,0 +1,8 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.Extensions.Common.Localization;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes;
+
+[NodeViewModel("SAMPLE_IMAGE", "IMAGE", "\ue907")]
+internal class SampleImageNodeViewModel : NodeViewModel<SampleImageNode>;

+ 7 - 0
src/PixiEditor/ViewModels/Document/Nodes/Shapes/DistributePointsNodeViewModel.cs

@@ -0,0 +1,7 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes.Shapes;
+
+[NodeViewModel("DISTRIBUTE_POINTS", "SHAPE", "\ue90a")]
+internal class DistributePointsNodeViewModel : NodeViewModel<DistributePointsNode>;

+ 7 - 0
src/PixiEditor/ViewModels/Document/Nodes/Shapes/EllipseNodeViewModel.cs

@@ -0,0 +1,7 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes.Shapes;
+
+[NodeViewModel("ELLIPSE_NODE", "SHAPE", "\ue90d")]
+internal class EllipseNodeViewModel : NodeViewModel<EllipseNode>;

+ 7 - 0
src/PixiEditor/ViewModels/Document/Nodes/Shapes/RasterizeShapeNodeViewModel.cs

@@ -0,0 +1,7 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes.Shapes;
+
+[NodeViewModel("RASTERIZE_SHAPE", "SHAPE", "\ue906")]
+internal class RasterizeShapeNodeViewModel : NodeViewModel<RasterizeShapeNode>;

+ 7 - 0
src/PixiEditor/ViewModels/Document/Nodes/Shapes/RemoveClosePointsNodeViewModel.cs

@@ -0,0 +1,7 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes.Shapes;
+
+[NodeViewModel("REMOVE_CLOSE_POINTS", "SHAPE", "\ue914")]
+internal class RemoveClosePointsNodeViewModel : NodeViewModel<RemoveClosePointsNode>;

+ 7 - 19
src/PixiEditor/ViewModels/Document/StructureMemberViewModel.cs → src/PixiEditor/ViewModels/Document/Nodes/StructureMemberViewModel.cs

@@ -1,12 +1,6 @@
-using System.Collections.ObjectModel;
-using Avalonia.Media.Imaging;
-using ChunkyImageLib;
-using CommunityToolkit.Mvvm.ComponentModel;
-using PixiEditor.Views.Nodes;
-using PixiEditor.ChangeableDocument.Actions.Generated;
+using PixiEditor.ChangeableDocument.Actions.Generated;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core;
-using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.Extensions.FlyUI.Elements;
 using PixiEditor.Helpers;
 using PixiEditor.Helpers;
 using PixiEditor.Models.DocumentModels;
 using PixiEditor.Models.DocumentModels;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers;
@@ -15,9 +9,9 @@ using PixiEditor.Numerics;
 using PixiEditor.ViewModels.Nodes;
 using PixiEditor.ViewModels.Nodes;
 using BlendMode = PixiEditor.ChangeableDocument.Enums.BlendMode;
 using BlendMode = PixiEditor.ChangeableDocument.Enums.BlendMode;
 
 
-namespace PixiEditor.ViewModels.Document;
+namespace PixiEditor.ViewModels.Document.Nodes;
 #nullable enable
 #nullable enable
-internal abstract class StructureMemberViewModel : NodeViewModel, IStructureMemberHandler
+internal abstract class StructureMemberViewModel<T> : NodeViewModel<T>, IStructureMemberHandler where T : Node
 {
 {
     public StructureMemberViewModel()
     public StructureMemberViewModel()
     {
     {
@@ -160,7 +154,10 @@ internal abstract class StructureMemberViewModel : NodeViewModel, IStructureMemb
     }
     }
 
 
     IDocument IStructureMemberHandler.Document => Document;
     IDocument IStructureMemberHandler.Document => Document;
+}
 
 
+public static class StructureMemberViewModel
+{
     /// <summary>
     /// <summary>
     /// Calculates the size of a scaled-down preview for a given size of layer tight bounds.
     /// Calculates the size of a scaled-down preview for a given size of layer tight bounds.
     /// </summary>
     /// </summary>
@@ -172,13 +169,4 @@ internal abstract class StructureMemberViewModel : NodeViewModel, IStructureMemb
             ? new VecI(Math.Max((int)Math.Round(prSize / proportions), 1), prSize)
             ? new VecI(Math.Max((int)Math.Round(prSize / proportions), 1), prSize)
             : new VecI(prSize, Math.Max((int)Math.Round(prSize * proportions), 1));
             : new VecI(prSize, Math.Max((int)Math.Round(prSize * proportions), 1));
     }
     }
-
-    public StructureMemberViewModel(DocumentViewModel doc, DocumentInternalParts internals, Guid id)
-    {
-        Document = doc;
-        Internals = internals;
-
-        this.id = id;
-        PreviewSurface = null;
-    }
 }
 }

+ 22 - 2
src/PixiEditor/ViewModels/Nodes/NodePropertyViewModel.cs

@@ -4,6 +4,7 @@ using Avalonia.Media;
 using Avalonia.Styling;
 using Avalonia.Styling;
 using PixiEditor.DrawingApi.Core.Shaders.Generation;
 using PixiEditor.DrawingApi.Core.Shaders.Generation;
 using PixiEditor.Models.DocumentModels;
 using PixiEditor.Models.DocumentModels;
+using PixiEditor.Models.Events;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers;
 using PixiEditor.ViewModels.Nodes.Properties;
 using PixiEditor.ViewModels.Nodes.Properties;
 
 
@@ -12,6 +13,7 @@ namespace PixiEditor.ViewModels.Nodes;
 internal abstract class NodePropertyViewModel : ViewModelBase, INodePropertyHandler
 internal abstract class NodePropertyViewModel : ViewModelBase, INodePropertyHandler
 {
 {
     private string propertyName;
     private string propertyName;
+    private bool isVisible = true;
     private string displayName;
     private string displayName;
     private object? _value;
     private object? _value;
     private INodeHandler node;
     private INodeHandler node;
@@ -33,9 +35,12 @@ internal abstract class NodePropertyViewModel : ViewModelBase, INodePropertyHand
         get => _value;
         get => _value;
         set
         set
         {
         {
+            var oldValue = _value;
+            
             if (SetProperty(ref _value, value))
             if (SetProperty(ref _value, value))
             {
             {
                 ViewModelMain.Current.NodeGraphManager.UpdatePropertyValue((node, PropertyName, value));
                 ViewModelMain.Current.NodeGraphManager.UpdatePropertyValue((node, PropertyName, value));
+                ValueChanged?.Invoke(this, new NodePropertyValueChangedArgs(oldValue, value));
             }
             }
         }
         }
     }
     }
@@ -58,6 +63,12 @@ internal abstract class NodePropertyViewModel : ViewModelBase, INodePropertyHand
         set => SetProperty(ref isFunc, value);
         set => SetProperty(ref isFunc, value);
     }
     }
 
 
+    public bool IsVisible
+    {
+        get => isVisible;
+        set => SetProperty(ref isVisible, value);
+    }
+
     public INodePropertyHandler? ConnectedOutput
     public INodePropertyHandler? ConnectedOutput
     {
     {
         get => connectedOutput;
         get => connectedOutput;
@@ -161,9 +172,18 @@ internal abstract class NodePropertyViewModel : ViewModelBase, INodePropertyHand
         
         
         return (NodePropertyViewModel)Activator.CreateInstance(viewModelType, node, type);
         return (NodePropertyViewModel)Activator.CreateInstance(viewModelType, node, type);
     }
     }
-
-    public void InternalSetValue(object? value) => SetProperty(ref _value, value, nameof(Value));
     
     
+    public event NodePropertyValueChanged? ValueChanged;
+
+    public void InternalSetValue(object? value)
+    {
+        var oldValue = _value;
+        if (SetProperty(ref _value, value, nameof(Value)))
+        {
+            ValueChanged?.Invoke(this, new NodePropertyValueChangedArgs(oldValue, value));
+        }
+    }
+
     private static bool IsShaderType(Type type)
     private static bool IsShaderType(Type type)
     {
     {
         return type.IsAssignableTo(typeof(ShaderExpressionVariable));
         return type.IsAssignableTo(typeof(ShaderExpressionVariable));

+ 27 - 11
src/PixiEditor/ViewModels/Nodes/NodeTypeInfo.cs

@@ -1,15 +1,13 @@
 using System.Reflection;
 using System.Reflection;
 using PixiEditor.ChangeableDocument.Changeables.Graph;
 using PixiEditor.ChangeableDocument.Changeables.Graph;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.Extensions.Common.Localization;
 using PixiEditor.Extensions.Common.Localization;
-using PixiEditor.Fonts;
 using PixiEditor.UI.Common.Fonts;
 using PixiEditor.UI.Common.Fonts;
 
 
 namespace PixiEditor.ViewModels.Nodes;
 namespace PixiEditor.ViewModels.Nodes;
 
 
 public class NodeTypeInfo
 public class NodeTypeInfo
 {
 {
-    public string UniqueName { get; }
-    
     public string DisplayName { get; }
     public string DisplayName { get; }
     
     
     public string? PickerName { get; }
     public string? PickerName { get; }
@@ -20,26 +18,44 @@ public class NodeTypeInfo
 
 
     public bool Hidden => PickerName is { Length: 0 };
     public bool Hidden => PickerName is { Length: 0 };
     
     
+    public Type NodeViewModelType { get; }
+    
     public Type NodeType { get; }
     public Type NodeType { get; }
     
     
     public string Icon { get; }
     public string Icon { get; }
 
 
-    public NodeTypeInfo(Type type)
+    public NodeTypeInfo(Type viewModelType)
     {
     {
-        NodeType = type;
-
-        var attribute = type.GetCustomAttribute<NodeInfoAttribute>();
+        NodeViewModelType = viewModelType;
+        NodeType = GetNodeType(NodeViewModelType.BaseType);
+        
+        var attribute = viewModelType.GetCustomAttribute<NodeViewModelAttribute>();
 
 
-        UniqueName = attribute.UniqueName;
         DisplayName = attribute.DisplayName;
         DisplayName = attribute.DisplayName;
         PickerName = attribute.PickerName;
         PickerName = attribute.PickerName;
         Category = attribute.Category ?? "";
         Category = attribute.Category ?? "";
+        Icon = attribute.Icon;
+
+        FinalPickerName = (PickerName ?? DisplayName);
+    }
 
 
-        if (NodeIcons.IconMap.TryGetValue(type, out var icon))
+    private Type GetNodeType(Type? baseType)
+    {
+        while (baseType != null)
         {
         {
-            Icon = icon;
+            if (baseType.IsGenericType)
+            {
+                var genericArgument = baseType.GetGenericArguments()[0];
+
+                if (genericArgument.IsAssignableTo(typeof(Node)))
+                {
+                    return genericArgument;
+                }
+            }
+
+            baseType = baseType.BaseType;
         }
         }
 
 
-        FinalPickerName = (PickerName ?? DisplayName);
+        throw new NullReferenceException($"Could not find node type of '{baseType}' in base classes");
     }
     }
 }
 }

+ 48 - 10
src/PixiEditor/ViewModels/Nodes/NodeViewModel.cs

@@ -1,10 +1,12 @@
 using System.Collections.ObjectModel;
 using System.Collections.ObjectModel;
+using System.Reflection;
 using Avalonia;
 using Avalonia;
 using Avalonia.Media;
 using Avalonia.Media;
 using ChunkyImageLib;
 using ChunkyImageLib;
 using CommunityToolkit.Mvvm.ComponentModel;
 using CommunityToolkit.Mvvm.ComponentModel;
 using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
 using PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
 using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core;
@@ -16,10 +18,12 @@ using PixiEditor.Numerics;
 using PixiEditor.ViewModels.Document;
 using PixiEditor.ViewModels.Document;
 
 
 namespace PixiEditor.ViewModels.Nodes;
 namespace PixiEditor.ViewModels.Nodes;
-internal class NodeViewModel : ObservableObject, INodeHandler
+
+internal abstract class NodeViewModel : ObservableObject, INodeHandler
 {
 {
+    private LocalizedString displayName;
     private IBrush? categoryBrush;
     private IBrush? categoryBrush;
-    private string nodeNameBindable;
+    private string? nodeNameBindable;
     private VecD position;
     private VecD position;
     private ObservableRangeCollection<INodePropertyHandler> inputs = new();
     private ObservableRangeCollection<INodePropertyHandler> inputs = new();
     private ObservableRangeCollection<INodePropertyHandler> outputs = new();
     private ObservableRangeCollection<INodePropertyHandler> outputs = new();
@@ -28,15 +32,25 @@ internal class NodeViewModel : ObservableObject, INodeHandler
 
 
     protected Guid id;
     protected Guid id;
 
 
-    public Guid Id
+    public Guid Id { get => id; private set => id = value; }
+
+    public LocalizedString DisplayName
     {
     {
-        get => id;
-        init => id = value;
+        get => displayName;
+        set
+        {
+            if (SetProperty(ref displayName, value) && nodeNameBindable == null)
+            {
+                OnPropertyChanged(nameof(NodeNameBindable));
+            }
+        }
     }
     }
+    
+    public string Category { get; }
 
 
     public string NodeNameBindable
     public string NodeNameBindable
     {
     {
-        get => nodeNameBindable;
+        get => nodeNameBindable ?? DisplayName;
         set
         set
         {
         {
             if (!Document.UpdateableChangeActive)
             if (!Document.UpdateableChangeActive)
@@ -47,7 +61,7 @@ internal class NodeViewModel : ObservableObject, INodeHandler
         } 
         } 
     }
     }
 
 
-    public string InternalName { get; init; }
+    public string InternalName { get; private set; }
 
 
     public IBrush CategoryBackgroundBrush
     public IBrush CategoryBackgroundBrush
     {
     {
@@ -55,7 +69,7 @@ internal class NodeViewModel : ObservableObject, INodeHandler
         {
         {
             if (categoryBrush == null)
             if (categoryBrush == null)
             {
             {
-                if (Metadata?.Category != null && Application.Current.Styles.TryGetResource($"{Stylize(Metadata.Category)}CategoryBackgroundBrush", App.Current.ActualThemeVariant, out var brushObj) && brushObj is IBrush brush)
+                if (!string.IsNullOrWhiteSpace(Category) && Application.Current.Styles.TryGetResource($"{Stylize(Category)}CategoryBackgroundBrush", App.Current.ActualThemeVariant, out var brushObj) && brushObj is IBrush brush)
                 {
                 {
                     categoryBrush = brush;
                     categoryBrush = brush;
                 }
                 }
@@ -108,13 +122,25 @@ internal class NodeViewModel : ObservableObject, INodeHandler
         set => SetProperty(ref isSelected, value);
         set => SetProperty(ref isSelected, value);
     }
     }
 
 
-    internal DocumentViewModel Document { get; init; }
-    internal DocumentInternalParts Internals { get; init; }
+    internal DocumentViewModel Document { get; private set; }
+    internal DocumentInternalParts Internals { get; private set; }
+
+    public void Initialize(Guid id, string internalName, DocumentViewModel document, DocumentInternalParts internals)
+    {
+        Id = id;
+        InternalName = internalName;
+        Document = document;
+        Internals = internals;
+    }
     
     
+    public virtual void OnInitialized() { }
     
     
     public NodeViewModel()
     public NodeViewModel()
     {
     {
+        var attribute = GetType().GetCustomAttribute<NodeViewModelAttribute>();
         
         
+        displayName = attribute.DisplayName;
+        Category = attribute.Category;
     }
     }
 
 
     public NodeViewModel(string nodeNameBindable, Guid id, VecD position, DocumentViewModel document, DocumentInternalParts internals)
     public NodeViewModel(string nodeNameBindable, Guid id, VecD position, DocumentViewModel document, DocumentInternalParts internals)
@@ -323,8 +349,20 @@ internal class NodeViewModel : ObservableObject, INodeHandler
         return Inputs.FirstOrDefault(x => x.PropertyName == propName) as NodePropertyViewModel;
         return Inputs.FirstOrDefault(x => x.PropertyName == propName) as NodePropertyViewModel;
     }
     }
     
     
+    public NodePropertyViewModel<T> FindInputProperty<T>(string propName)
+    {
+        return Inputs.FirstOrDefault(x => x.PropertyName == propName) as NodePropertyViewModel<T>;
+    }
+
     public NodePropertyViewModel FindOutputProperty(string propName)
     public NodePropertyViewModel FindOutputProperty(string propName)
     {
     {
         return Outputs.FirstOrDefault(x => x.PropertyName == propName) as NodePropertyViewModel;
         return Outputs.FirstOrDefault(x => x.PropertyName == propName) as NodePropertyViewModel;
     }
     }
+
+    public NodePropertyViewModel<T> FindOutputProperty<T>(string propName)
+    {
+        return Outputs.FirstOrDefault(x => x.PropertyName == propName) as NodePropertyViewModel<T>;
+    }
 }
 }
+
+internal abstract class NodeViewModel<T> : NodeViewModel where T : Node { }

+ 12 - 0
src/PixiEditor/ViewModels/Nodes/NodeViewModelAttribute.cs

@@ -0,0 +1,12 @@
+namespace PixiEditor.ViewModels.Nodes;
+
+public class NodeViewModelAttribute(string displayName, string? category, string? icon) : Attribute
+{
+    public string DisplayName { get; } = displayName;
+
+    public string? Category { get; } = category;
+
+    public string? Icon { get; } = icon;
+
+    public string? PickerName { get; set; }
+}

+ 5 - 4
src/PixiEditor/ViewModels/SubViewModels/LayersViewModel.cs

@@ -25,6 +25,7 @@ using PixiEditor.Numerics;
 using PixiEditor.UI.Common.Fonts;
 using PixiEditor.UI.Common.Fonts;
 using PixiEditor.ViewModels.Dock;
 using PixiEditor.ViewModels.Dock;
 using PixiEditor.ViewModels.Document;
 using PixiEditor.ViewModels.Document;
+using PixiEditor.ViewModels.Document.Nodes;
 
 
 namespace PixiEditor.ViewModels.SubViewModels;
 namespace PixiEditor.ViewModels.SubViewModels;
 #nullable enable
 #nullable enable
@@ -131,7 +132,7 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
     public void ToggleLockTransparency()
     public void ToggleLockTransparency()
     {
     {
         var member = Owner.DocumentManagerSubViewModel.ActiveDocument?.SelectedStructureMember;
         var member = Owner.DocumentManagerSubViewModel.ActiveDocument?.SelectedStructureMember;
-        if (member is not LayerViewModel layerVm)
+        if (member is not ImageLayerNodeViewModel layerVm)
             return;
             return;
         layerVm.LockTransparencyBindable = !layerVm.LockTransparencyBindable;
         layerVm.LockTransparencyBindable = !layerVm.LockTransparencyBindable;
     }
     }
@@ -171,7 +172,7 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
     public void DuplicateLayer()
     public void DuplicateLayer()
     {
     {
         var member = Owner.DocumentManagerSubViewModel.ActiveDocument?.SelectedStructureMember;
         var member = Owner.DocumentManagerSubViewModel.ActiveDocument?.SelectedStructureMember;
-        if (member is not LayerViewModel layerVM)
+        if (member is not ILayerHandler)
             return;
             return;
         member.Document.Operations.DuplicateLayer(member.Id);
         member.Document.Operations.DuplicateLayer(member.Id);
     }
     }
@@ -180,7 +181,7 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
     public bool SelectedMemberIsLayer(object property)
     public bool SelectedMemberIsLayer(object property)
     {
     {
         var member = Owner.DocumentManagerSubViewModel.ActiveDocument?.SelectedStructureMember;
         var member = Owner.DocumentManagerSubViewModel.ActiveDocument?.SelectedStructureMember;
-        return member is LayerViewModel;
+        return member is ILayerHandler;
     }
     }
 
 
     private bool HasSelectedMember(bool above)
     private bool HasSelectedMember(bool above)
@@ -206,7 +207,7 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
         var path = doc!.StructureHelper.FindPath(member.Id);
         var path = doc!.StructureHelper.FindPath(member.Id);
         if (path.Count < 2)
         if (path.Count < 2)
             return;
             return;
-        var parent = (FolderViewModel)path[1];
+        var parent = (FolderNodeViewModel)path[1];
         int curIndex = parent.Children.IndexOf(path[0]);
         int curIndex = parent.Children.IndexOf(path[0]);
         if (upwards)
         if (upwards)
         {
         {

+ 4 - 3
src/PixiEditor/Views/Layers/FolderControl.axaml.cs

@@ -6,16 +6,17 @@ using Avalonia.Media;
 using PixiEditor.Models.Controllers.InputDevice;
 using PixiEditor.Models.Controllers.InputDevice;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Layers;
 using PixiEditor.ViewModels.Document;
 using PixiEditor.ViewModels.Document;
+using PixiEditor.ViewModels.Document.Nodes;
 
 
 namespace PixiEditor.Views.Layers;
 namespace PixiEditor.Views.Layers;
 #nullable enable
 #nullable enable
 internal partial class FolderControl : UserControl
 internal partial class FolderControl : UserControl
 {
 {
 
 
-    public static readonly StyledProperty<FolderViewModel> FolderProperty =
-        AvaloniaProperty.Register<FolderControl, FolderViewModel>(nameof(Folder));
+    public static readonly StyledProperty<FolderNodeViewModel> FolderProperty =
+        AvaloniaProperty.Register<FolderControl, FolderNodeViewModel>(nameof(Folder));
 
 
-    public FolderViewModel Folder
+    public FolderNodeViewModel Folder
     {
     {
         get => GetValue(FolderProperty);
         get => GetValue(FolderProperty);
         set => SetValue(FolderProperty, value);
         set => SetValue(FolderProperty, value);

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

@@ -7,6 +7,7 @@ using CommunityToolkit.Mvvm.Input;
 using PixiEditor.Models.Controllers.InputDevice;
 using PixiEditor.Models.Controllers.InputDevice;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Layers;
 using PixiEditor.ViewModels.Document;
 using PixiEditor.ViewModels.Document;
+using PixiEditor.ViewModels.Document.Nodes;
 
 
 namespace PixiEditor.Views.Layers;
 namespace PixiEditor.Views.Layers;
 #nullable enable
 #nullable enable
@@ -14,10 +15,10 @@ internal partial class LayerControl : UserControl
 {
 {
     public static string? LayerControlDataName = typeof(LayerControl).FullName;
     public static string? LayerControlDataName = typeof(LayerControl).FullName;
 
 
-    public static readonly StyledProperty<LayerViewModel> LayerProperty =
-        AvaloniaProperty.Register<LayerControl, LayerViewModel>(nameof(Layer));
+    public static readonly StyledProperty<ImageLayerNodeViewModel> LayerProperty =
+        AvaloniaProperty.Register<LayerControl, ImageLayerNodeViewModel>(nameof(Layer));
 
 
-    public LayerViewModel Layer
+    public ImageLayerNodeViewModel Layer
     {
     {
         get => GetValue(LayerProperty);
         get => GetValue(LayerProperty);
         set => SetValue(LayerProperty, value);
         set => SetValue(LayerProperty, value);

+ 3 - 2
src/PixiEditor/Views/Layers/LayersManager.axaml

@@ -14,6 +14,7 @@
              xmlns:docVm="clr-namespace:PixiEditor.ViewModels.Document"
              xmlns:docVm="clr-namespace:PixiEditor.ViewModels.Document"
              xmlns:ui1="clr-namespace:PixiEditor.Helpers.UI"
              xmlns:ui1="clr-namespace:PixiEditor.Helpers.UI"
              xmlns:panels="clr-namespace:PixiEditor.Views.Panels"
              xmlns:panels="clr-namespace:PixiEditor.Views.Panels"
+             xmlns:nodes="clr-namespace:PixiEditor.ViewModels.Document.Nodes"
              mc:Ignorable="d"
              mc:Ignorable="d"
              d:DesignHeight="450" d:DesignWidth="250" x:Name="layersManager">
              d:DesignHeight="450" d:DesignWidth="250" x:Name="layersManager">
     <UserControl.Resources>
     <UserControl.Resources>
@@ -137,14 +138,14 @@
 
 
                 </TreeView.Resources>
                 </TreeView.Resources>
                 <TreeView.DataTemplates>
                 <TreeView.DataTemplates>
-                    <TreeDataTemplate DataType="docVm:FolderViewModel" ItemsSource="{Binding Children}">
+                    <TreeDataTemplate DataType="nodes:FolderNodeViewModel" ItemsSource="{Binding Children}">
                         <layers:FolderControl
                         <layers:FolderControl
                                 Folder="{Binding}"
                                 Folder="{Binding}"
                                 Manager="{Binding ElementName=layersManager}"
                                 Manager="{Binding ElementName=layersManager}"
                                 PointerPressed="FolderControl_MouseDown"
                                 PointerPressed="FolderControl_MouseDown"
                                 PointerReleased="FolderControl_MouseUp"/>
                                 PointerReleased="FolderControl_MouseUp"/>
                     </TreeDataTemplate>
                     </TreeDataTemplate>
-                    <DataTemplate DataType="{x:Type docVm:LayerViewModel}">
+                    <DataTemplate DataType="{x:Type nodes:ImageLayerNodeViewModel}">
                         <layers:LayerControl
                         <layers:LayerControl
                             Layer="{Binding}"
                             Layer="{Binding}"
                             Manager="{Binding ElementName=layersManager}"
                             Manager="{Binding ElementName=layersManager}"

+ 3 - 2
src/PixiEditor/Views/Layers/LayersManager.axaml.cs

@@ -10,6 +10,7 @@ using PixiEditor.Models.Layers;
 using PixiEditor.ViewModels;
 using PixiEditor.ViewModels;
 using PixiEditor.ViewModels.Dock;
 using PixiEditor.ViewModels.Dock;
 using PixiEditor.ViewModels.Document;
 using PixiEditor.ViewModels.Document;
+using PixiEditor.ViewModels.Document.Nodes;
 using PixiEditor.ViewModels.Nodes;
 using PixiEditor.ViewModels.Nodes;
 
 
 namespace PixiEditor.Views.Layers;
 namespace PixiEditor.Views.Layers;
@@ -180,7 +181,7 @@ internal partial class LayersManager : UserControl
         ((Border)sender).BorderBrush = Brushes.Transparent;
         ((Border)sender).BorderBrush = Brushes.Transparent;
     }
     }
 
 
-    private void HandleMouseDown(StructureMemberViewModel memberVM, PointerPressedEventArgs pointerPressedEventArgs)
+    private void HandleMouseDown(IStructureMemberHandler memberVM, PointerPressedEventArgs pointerPressedEventArgs)
     {
     {
         if (ActiveDocument is null)
         if (ActiveDocument is null)
             return;
             return;
@@ -227,7 +228,7 @@ internal partial class LayersManager : UserControl
         var reversed = root.Reverse();
         var reversed = root.Reverse();
         foreach (var child in reversed)
         foreach (var child in reversed)
         {
         {
-            if (child is FolderViewModel innerFolder)
+            if (child is FolderNodeViewModel innerFolder)
             {
             {
                 matches = TraverseRange(bound1, bound2, innerFolder.Children, action, matches);
                 matches = TraverseRange(bound1, bound2, innerFolder.Children, action, matches);
             }
             }

+ 1 - 1
src/PixiEditor/Views/Nodes/NodeGraphView.cs

@@ -192,7 +192,7 @@ internal class NodeGraphView : Zoombox.Zoombox
         SocketDropCommand = new RelayCommand<NodeSocket>(SocketDrop);
         SocketDropCommand = new RelayCommand<NodeSocket>(SocketDrop);
         CreateNodeFromContextCommand = new RelayCommand<NodeTypeInfo>(CreateNodeType);
         CreateNodeFromContextCommand = new RelayCommand<NodeTypeInfo>(CreateNodeType);
 
 
-        AllNodeTypes = new ObservableCollection<Type>(GatherAssemblyTypes<Node>());
+        AllNodeTypes = new ObservableCollection<Type>(GatherAssemblyTypes<NodeViewModel>());
         AllNodeTypeInfos = new ObservableCollection<NodeTypeInfo>(AllNodeTypes.Select(x => new NodeTypeInfo(x)));
         AllNodeTypeInfos = new ObservableCollection<NodeTypeInfo>(AllNodeTypes.Select(x => new NodeTypeInfo(x)));
     }
     }