Browse Source

Properties Generation wip

flabbet 1 year ago
parent
commit
bd9bc6a0c8
28 changed files with 244 additions and 145 deletions
  1. 21 3
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/DocumentUpdater.cs
  2. 0 7
      src/PixiEditor.AvaloniaUI/Models/Handlers/IInputPropertyHandler.cs
  3. 3 2
      src/PixiEditor.AvaloniaUI/Models/Handlers/INodeHandler.cs
  4. 5 1
      src/PixiEditor.AvaloniaUI/Models/Handlers/INodePropertyHandler.cs
  5. 0 9
      src/PixiEditor.AvaloniaUI/Models/Handlers/IOutputPropertyHandler.cs
  6. 19 7
      src/PixiEditor.AvaloniaUI/Styles/Templates/NodePropertyViewTemplate.axaml
  7. 5 1
      src/PixiEditor.AvaloniaUI/Styles/Templates/NodeSocket.axaml
  8. 2 2
      src/PixiEditor.AvaloniaUI/ViewModels/Document/NodeGraphViewModel.cs
  9. 38 6
      src/PixiEditor.AvaloniaUI/ViewModels/Nodes/NodePropertyViewModel.cs
  10. 7 6
      src/PixiEditor.AvaloniaUI/ViewModels/Nodes/NodeViewModel.cs
  11. 10 0
      src/PixiEditor.AvaloniaUI/ViewModels/Nodes/Properties/GenericPropertyViewModel.cs
  12. 0 17
      src/PixiEditor.AvaloniaUI/ViewModels/Nodes/Properties/ImageNodePropertyViewModel.cs
  13. 1 7
      src/PixiEditor.AvaloniaUI/Views/Nodes/NodeGraphView.cs
  14. 7 5
      src/PixiEditor.AvaloniaUI/Views/Nodes/NodeView.cs
  15. 8 0
      src/PixiEditor.AvaloniaUI/Views/Nodes/Properties/GenericPropertyView.axaml
  16. 14 0
      src/PixiEditor.AvaloniaUI/Views/Nodes/Properties/GenericPropertyView.axaml.cs
  17. 0 10
      src/PixiEditor.AvaloniaUI/Views/Nodes/Properties/ImageNodePropertyView.axaml
  18. 0 38
      src/PixiEditor.AvaloniaUI/Views/Nodes/Properties/ImageNodePropertyView.axaml.cs
  19. 16 1
      src/PixiEditor.AvaloniaUI/Views/Nodes/Properties/NodeSocket.cs
  20. 6 4
      src/PixiEditor.ChangeableDocument/ChangeInfos/NodeGraph/CreateNode_ChangeInfo.cs
  21. 7 0
      src/PixiEditor.ChangeableDocument/ChangeInfos/NodeGraph/NodePropertyInfo.cs
  22. 7 2
      src/PixiEditor.ChangeableDocument/ChangeInfos/Structure/CreateLayer_ChangeInfo.cs
  23. 9 3
      src/PixiEditor.ChangeableDocument/ChangeInfos/Structure/CreateStructureMember_ChangeInfo.cs
  24. 6 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/InputProperty.cs
  25. 3 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/INodeProperty.cs
  26. 7 5
      src/PixiEditor.ChangeableDocument/Changeables/Graph/OutputProperty.cs
  27. 13 2
      src/PixiEditor.ChangeableDocument/Changes/NodeGraph/CreateNode_Change.cs
  28. 30 3
      src/PixiEditor.ChangeableDocument/Changes/Structure/CreateStructureMember_Change.cs

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

@@ -1,4 +1,6 @@
-using ChunkyImageLib;
+using System.Collections.Immutable;
+using System.Collections.ObjectModel;
+using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
 using PixiEditor.AvaloniaUI.Helpers;
 using PixiEditor.AvaloniaUI.Models.DocumentPassthroughActions;
@@ -355,8 +357,6 @@ internal class DocumentUpdater
         memberVM.SetMaskIsVisible(info.MaskIsVisible);
         memberVM.SetBlendMode(info.BlendMode);
         
-        doc.NodeGraphHandler.AddNode(memberVM);
-
         //parentFolderVM.Children.Insert(info.Index, memberVM);
 
         /*if (info is CreateFolder_ChangeInfo folderInfo)
@@ -471,9 +471,27 @@ internal class DocumentUpdater
     private void ProcessCreateNode(CreateNode_ChangeInfo info)
     {
         NodeViewModel node = new NodeViewModel(info.NodeName, info.Id, info.Position);
+        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 List<INodePropertyHandler> CreateProperties(ImmutableArray<NodePropertyInfo> source, NodeViewModel node, bool isInput)
+    {
+        List<INodePropertyHandler> inputs = new();
+        foreach (var input in source)
+        {
+            var prop = NodePropertyViewModel.CreateFromType(input.ValueType, node);
+            prop.Name = input.Name;
+            prop.IsInput = isInput;
+            inputs.Add(prop);
+        }
+        
+        return inputs;
+    }
+    
     private void ProcessDeleteNode(DeleteNode_ChangeInfo info)
     {
         doc.NodeGraphHandler.RemoveNode(info.Id);

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

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

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

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

+ 5 - 1
src/PixiEditor.AvaloniaUI/Models/Handlers/INodePropertyHandler.cs

@@ -1,9 +1,13 @@
-namespace PixiEditor.AvaloniaUI.Models.Handlers;
+using System.Collections.ObjectModel;
+
+namespace PixiEditor.AvaloniaUI.Models.Handlers;
 
 public interface INodePropertyHandler
 {
     public string Name { get; set; }
     public object Value { get; set; }
     public bool IsInput { get; }
+    public INodePropertyHandler? ConnectedOutput { get; set; }
+    public ObservableCollection<INodePropertyHandler> ConnectedInputs { get; }
     public INodeHandler Node { get; set; }
 }

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

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

+ 19 - 7
src/PixiEditor.AvaloniaUI/Styles/Templates/NodePropertyViewTemplate.axaml

@@ -1,18 +1,30 @@
 <Styles xmlns="https://github.com/avaloniaui"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:properties="clr-namespace:PixiEditor.AvaloniaUI.Views.Nodes.Properties">
-    
+
     <Style Selector="properties|NodePropertyView">
         <Setter Property="ClipToBounds" Value="False" />
         <Setter Property="Template">
             <ControlTemplate>
                 <Grid Margin="-10, 0">
-                    <Grid.ColumnDefinitions>10, *, 10</Grid.ColumnDefinitions>
-                    <properties:NodeSocket Name="PART_InputSocket" IsVisible="{Binding DataContext.IsInput, 
-                    RelativeSource={RelativeSource TemplatedParent}}"/>
-                    <ContentPresenter Grid.Column="1" Content="{TemplateBinding Content}"/>
-                    <properties:NodeSocket Grid.Column="2" Name="PART_OutputSocket" IsVisible="{Binding !DataContext.IsInput,
-                    RelativeSource={RelativeSource TemplatedParent}}"/>
+                    <Grid.ColumnDefinitions>10*, *, 10*</Grid.ColumnDefinitions>
+                    <properties:NodeSocket Name="PART_InputSocket"
+                                           Label="{Binding DataContext.Name, RelativeSource={RelativeSource TemplatedParent}}"
+                                           IsVisible="{Binding DataContext.IsInput, 
+                    RelativeSource={RelativeSource TemplatedParent}}">
+                        <properties:NodeSocket.IsInput>
+                            <x:Boolean>True</x:Boolean>
+                        </properties:NodeSocket.IsInput>
+                    </properties:NodeSocket>
+                    <ContentPresenter Grid.Column="1" Content="{TemplateBinding Content}" />
+                    <properties:NodeSocket Grid.Column="2" Name="PART_OutputSocket"
+                                           Label="{Binding DataContext.Name, RelativeSource={RelativeSource TemplatedParent}}"
+                                           IsVisible="{Binding !DataContext.IsInput,
+                    RelativeSource={RelativeSource TemplatedParent}}">
+                        <properties:NodeSocket.IsInput>
+                            <x:Boolean>False</x:Boolean>
+                        </properties:NodeSocket.IsInput>
+                    </properties:NodeSocket>
                 </Grid>
             </ControlTemplate>
         </Setter>

+ 5 - 1
src/PixiEditor.AvaloniaUI/Styles/Templates/NodeSocket.axaml

@@ -5,7 +5,11 @@
     <ControlTheme TargetType="properties:NodeSocket" x:Key="{x:Type properties:NodeSocket}">
         <Setter Property="Template">
             <ControlTemplate>
-                <Ellipse Width="10" Height="10" Fill="Red"/>
+                <StackPanel Orientation="Horizontal">
+                    <TextBlock Text="{TemplateBinding Label}" IsVisible="{Binding !IsInput, RelativeSource={RelativeSource TemplatedParent}}"/>
+                    <Ellipse Width="10" Height="10" Fill="Red" />
+                    <TextBlock Text="{TemplateBinding Label}" IsVisible="{Binding IsInput, RelativeSource={RelativeSource TemplatedParent}}"/>
+                </StackPanel>
             </ControlTemplate>
         </Setter>
     </ControlTheme>

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

@@ -70,12 +70,12 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler
    
                foreach (var input in node.Inputs)
                {
-                   if (input.Connection == null)
+                   if (input.ConnectedOutput == null)
                    {
                        continue;
                    }
    
-                   queueNodes.Enqueue(input.Connection.Node);
+                   queueNodes.Enqueue(input.ConnectedOutput.Node);
                }
            }
    

+ 38 - 6
src/PixiEditor.AvaloniaUI/ViewModels/Nodes/NodePropertyViewModel.cs

@@ -1,14 +1,20 @@
-using Avalonia;
+using System.Collections.ObjectModel;
+using Avalonia;
+using PixiEditor.AvaloniaUI.Models.Handlers;
+using PixiEditor.AvaloniaUI.ViewModels.Nodes.Properties;
 
 namespace PixiEditor.AvaloniaUI.ViewModels.Nodes;
 
-public abstract class NodePropertyViewModel : ViewModelBase
+public abstract class NodePropertyViewModel : ViewModelBase, INodePropertyHandler
 {
     private string name;
     private object value;
-    private NodeViewModel node;
+    private INodeHandler node;
     private bool isInput;
     
+    private ObservableCollection<INodePropertyHandler> connectedInputs = new();
+    private INodePropertyHandler? connectedOutput;
+    
     public string Name
     {
         get => name;
@@ -26,17 +32,43 @@ public abstract class NodePropertyViewModel : ViewModelBase
         get => isInput;
         set => SetProperty(ref isInput, value);
     }
-    
-    public NodeViewModel Node
+
+    public INodePropertyHandler? ConnectedOutput
+    {
+        get => connectedOutput;
+        set => SetProperty(ref connectedOutput, value);
+    }
+
+    public ObservableCollection<INodePropertyHandler> ConnectedInputs
+    {
+        get => connectedInputs;
+        set => SetProperty(ref connectedInputs, value);
+    }
+
+    public INodeHandler Node
     {
         get => node;
         set => SetProperty(ref node, value);
     }
 
-    public NodePropertyViewModel(NodeViewModel node)
+    public NodePropertyViewModel(INodeHandler node)
     {
         Node = node;
     }
+
+    public static NodePropertyViewModel? CreateFromType(Type type, INodeHandler node)
+    {
+        string name = type.Name;
+        name += "PropertyViewModel";
+        
+        Type viewModelType = Type.GetType($"PixiEditor.AvaloniaUI.ViewModels.Nodes.Properties.{name}");
+        if (viewModelType == null)
+        {
+            return new GenericPropertyViewModel(node);
+        }
+        
+        return (NodePropertyViewModel)Activator.CreateInstance(viewModelType, node);
+    }
 }
 
 public abstract class NodePropertyViewModel<T> : NodePropertyViewModel

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

@@ -3,6 +3,7 @@ using Avalonia;
 using ChunkyImageLib;
 using CommunityToolkit.Mvvm.ComponentModel;
 using PixiEditor.AvaloniaUI.Models.Handlers;
+using PixiEditor.AvaloniaUI.Models.Structures;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.AvaloniaUI.ViewModels.Nodes;
@@ -11,8 +12,8 @@ public class NodeViewModel : ObservableObject, INodeHandler
 {
     private string nodeName;
     private VecD position;
-    private ObservableCollection<IInputPropertyHandler> inputs = new();
-    private ObservableCollection<IOutputPropertyHandler> outputs = new();
+    private ObservableRangeCollection<INodePropertyHandler> inputs = new();
+    private ObservableRangeCollection<INodePropertyHandler> outputs = new();
     private Surface resultPreview;
 
     protected Guid id;
@@ -46,13 +47,13 @@ public class NodeViewModel : ObservableObject, INodeHandler
         set => SetProperty(ref position, value);
     }
 
-    public ObservableCollection<IInputPropertyHandler> Inputs
+    public ObservableRangeCollection<INodePropertyHandler> Inputs
     {
         get => inputs;
         set => SetProperty(ref inputs, value);
     }
 
-    public ObservableCollection<IOutputPropertyHandler> Outputs
+    public ObservableRangeCollection<INodePropertyHandler> Outputs
     {
         get => outputs;
         set => SetProperty(ref outputs, value);
@@ -86,7 +87,7 @@ public class NodeViewModel : ObservableObject, INodeHandler
 
             foreach (var inputProperty in node.Inputs)
             {
-                if (inputProperty.Connection != null)
+                if (inputProperty.ConnectedOutput != null)
                 {
                     queueNodes.Enqueue(inputProperty.Node);
                 }
@@ -116,7 +117,7 @@ public class NodeViewModel : ObservableObject, INodeHandler
 
             foreach (var outputProperty in node.Outputs)
             {
-                foreach (var connection in outputProperty.Connections)
+                foreach (var connection in outputProperty.ConnectedInputs)
                 {
                     queueNodes.Enqueue(connection.Node);
                 }

+ 10 - 0
src/PixiEditor.AvaloniaUI/ViewModels/Nodes/Properties/GenericPropertyViewModel.cs

@@ -0,0 +1,10 @@
+using PixiEditor.AvaloniaUI.Models.Handlers;
+
+namespace PixiEditor.AvaloniaUI.ViewModels.Nodes.Properties;
+
+public class GenericPropertyViewModel : NodePropertyViewModel
+{
+    public GenericPropertyViewModel(INodeHandler node) : base(node)
+    {
+    }
+}

+ 0 - 17
src/PixiEditor.AvaloniaUI/ViewModels/Nodes/Properties/ImageNodePropertyViewModel.cs

@@ -1,17 +0,0 @@
-using ChunkyImageLib;
-
-namespace PixiEditor.AvaloniaUI.ViewModels.Nodes.Properties;
-
-public class ImageNodePropertyViewModel : NodePropertyViewModel<Surface>
-{
-    public ImageNodePropertyViewModel(NodeViewModel node) : base(node)
-    {
-        this.PropertyChanged += (sender, args) =>
-        {
-            if (args.PropertyName == nameof(Value))
-            {
-                Node.ResultPreview = Value;
-            }
-        };
-    }
-}

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

@@ -1,11 +1,5 @@
-using System.Collections.ObjectModel;
-using Avalonia;
-using Avalonia.Controls;
-using Avalonia.Controls.Primitives;
-using Avalonia.VisualTree;
+using Avalonia;
 using PixiEditor.AvaloniaUI.Models.Handlers;
-using PixiEditor.AvaloniaUI.ViewModels.Nodes;
-using PixiEditor.AvaloniaUI.ViewModels.Nodes.Properties;
 
 namespace PixiEditor.AvaloniaUI.Views.Nodes;
 

+ 7 - 5
src/PixiEditor.AvaloniaUI/Views/Nodes/NodeView.cs

@@ -4,6 +4,8 @@ using Avalonia.Controls;
 using Avalonia.Controls.Primitives;
 using Avalonia.VisualTree;
 using ChunkyImageLib;
+using PixiEditor.AvaloniaUI.Models.Handlers;
+using PixiEditor.AvaloniaUI.Models.Structures;
 using PixiEditor.AvaloniaUI.ViewModels.Nodes;
 using PixiEditor.AvaloniaUI.Views.Nodes.Properties;
 
@@ -14,10 +16,10 @@ public class NodeView : TemplatedControl
     public static readonly StyledProperty<string> DisplayNameProperty = AvaloniaProperty.Register<NodeView, string>(
         nameof(DisplayName), "Node");
 
-    public static readonly StyledProperty<ObservableCollection<NodePropertyViewModel>> InputsProperty = AvaloniaProperty.Register<NodeView, ObservableCollection<NodePropertyViewModel>>(
+    public static readonly StyledProperty<ObservableRangeCollection<INodePropertyHandler>> InputsProperty = AvaloniaProperty.Register<NodeView, ObservableRangeCollection<INodePropertyHandler>>(
         nameof(Inputs));
     
-    public static readonly StyledProperty<ObservableCollection<NodePropertyViewModel>> OutputsProperty = AvaloniaProperty.Register<NodeView, ObservableCollection<NodePropertyViewModel>>(
+    public static readonly StyledProperty<ObservableRangeCollection<INodePropertyHandler>> OutputsProperty = AvaloniaProperty.Register<NodeView, ObservableRangeCollection<INodePropertyHandler>>(
         nameof(Outputs));
 
     public static readonly StyledProperty<Surface> ResultPreviewProperty = AvaloniaProperty.Register<NodeView, Surface>(
@@ -29,13 +31,13 @@ public class NodeView : TemplatedControl
         set => SetValue( ResultPreviewProperty, value);
     }
 
-    public ObservableCollection<NodePropertyViewModel> Outputs
+    public ObservableRangeCollection<INodePropertyHandler> Outputs
     {
         get => GetValue(OutputsProperty);
         set => SetValue(OutputsProperty, value);
     }
 
-    public ObservableCollection<NodePropertyViewModel> Inputs
+    public ObservableRangeCollection<INodePropertyHandler> Inputs
     {
         get => GetValue(InputsProperty);
         set => SetValue(InputsProperty, value);
@@ -47,7 +49,7 @@ public class NodeView : TemplatedControl
         set => SetValue(DisplayNameProperty, value);
     }
 
-    public Point GetSocketPoint(NodePropertyViewModel property, Canvas canvas)
+    public Point GetSocketPoint(INodePropertyHandler property, Canvas canvas)
     {
         NodePropertyView propertyView = this.GetVisualDescendants().OfType<NodePropertyView>().FirstOrDefault(x => x.DataContext == property);
         

+ 8 - 0
src/PixiEditor.AvaloniaUI/Views/Nodes/Properties/GenericPropertyView.axaml

@@ -0,0 +1,8 @@
+<properties:NodePropertyView xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:properties="clr-namespace:PixiEditor.AvaloniaUI.Views.Nodes.Properties"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="PixiEditor.AvaloniaUI.Views.Nodes.Properties.GenericPropertyView">
+</properties:NodePropertyView>

+ 14 - 0
src/PixiEditor.AvaloniaUI/Views/Nodes/Properties/GenericPropertyView.axaml.cs

@@ -0,0 +1,14 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace PixiEditor.AvaloniaUI.Views.Nodes.Properties;
+
+public partial class GenericPropertyView : NodePropertyView
+{
+    public GenericPropertyView()
+    {
+        InitializeComponent();
+    }
+}
+

+ 0 - 10
src/PixiEditor.AvaloniaUI/Views/Nodes/Properties/ImageNodePropertyView.axaml

@@ -1,10 +0,0 @@
-<properties:NodePropertyView x:TypeArguments="chunkyImageLib:Surface" xmlns="https://github.com/avaloniaui"
-                             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-                             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-                             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-                             xmlns:properties="clr-namespace:PixiEditor.AvaloniaUI.Views.Nodes.Properties"
-                             xmlns:chunkyImageLib="clr-namespace:ChunkyImageLib;assembly=ChunkyImageLib"
-                             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" ClipToBounds="False"
-                             x:Class="PixiEditor.AvaloniaUI.Views.Nodes.Properties.ImageNodePropertyView">
-        <Button Content="Open Image (temp)" Click="Button_OnClick"/>
-</properties:NodePropertyView>

+ 0 - 38
src/PixiEditor.AvaloniaUI/Views/Nodes/Properties/ImageNodePropertyView.axaml.cs

@@ -1,38 +0,0 @@
-using Avalonia;
-using Avalonia.Controls;
-using Avalonia.Controls.ApplicationLifetimes;
-using Avalonia.Interactivity;
-using Avalonia.Markup.Xaml;
-using Avalonia.Platform.Storage;
-using ChunkyImageLib;
-using PixiEditor.AvaloniaUI.Helpers;
-using PixiEditor.AvaloniaUI.Models.IO;
-using PixiEditor.Numerics;
-
-namespace PixiEditor.AvaloniaUI.Views.Nodes.Properties;
-
-public partial class ImageNodePropertyView : NodePropertyView<Surface> 
-{
-    public ImageNodePropertyView()
-    {
-        InitializeComponent();
-    }
-
-    private void Button_OnClick(object? sender, RoutedEventArgs e)
-    {
-        var filter = SupportedFilesHelper.BuildOpenFilter();
-
-        if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
-        {
-            var dialog = desktop.MainWindow.StorageProvider.OpenFilePickerAsync(
-                new FilePickerOpenOptions { FileTypeFilter = filter });
-
-            var result = dialog.Result;
-
-            if (result.Count == 0 || !Importer.IsSupportedFile(result[0].Path.LocalPath))
-                return;
-
-            SetValue(Importer.ImportImage(result[0].Path.LocalPath, VecI.NegativeOne));
-        }
-    }
-}

+ 16 - 1
src/PixiEditor.AvaloniaUI/Views/Nodes/Properties/NodeSocket.cs

@@ -1,8 +1,23 @@
-using Avalonia.Controls.Primitives;
+using Avalonia;
+using Avalonia.Controls.Primitives;
 
 namespace PixiEditor.AvaloniaUI.Views.Nodes.Properties;
 
 public class NodeSocket : TemplatedControl
 {
+    public static readonly StyledProperty<bool> IsInputProperty = AvaloniaProperty.Register<NodeSocket, bool>("IsInput");
+    public static readonly StyledProperty<string> LabelProperty = AvaloniaProperty.Register<NodeSocket, string>("Label");
+
+    public bool IsInput
+    {
+        get { return (bool)GetValue(IsInputProperty); }
+        set { SetValue(IsInputProperty, value); }
+    }
+
+    public string Label
+    {
+        get { return (string)GetValue(LabelProperty); }
+        set { SetValue(LabelProperty, value); }
+    }
 }
 

+ 6 - 4
src/PixiEditor.ChangeableDocument/ChangeInfos/NodeGraph/CreateNode_ChangeInfo.cs

@@ -1,7 +1,9 @@
-using PixiEditor.Numerics;
+using System.Collections;
+using System.Collections.Immutable;
+using PixiEditor.Numerics;
 
 namespace PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
 
-public record CreateNode_ChangeInfo(string NodeName, VecD Position ,Guid Id) : IChangeInfo
-{
-}
+public record CreateNode_ChangeInfo(string NodeName, VecD Position, Guid Id, 
+    ImmutableArray<NodePropertyInfo> Inputs,
+    ImmutableArray<NodePropertyInfo> Outputs) : IChangeInfo;

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

@@ -0,0 +1,7 @@
+namespace PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
+
+public record NodePropertyInfo(
+    string Name,
+    Type ValueType,
+    bool IsInput,
+    Guid NodeId);

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

@@ -1,5 +1,7 @@
-using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using System.Collections.Immutable;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
 using PixiEditor.ChangeableDocument.Enums;
 
 namespace PixiEditor.ChangeableDocument.ChangeInfos.Structure;
@@ -16,7 +18,10 @@ public record class CreateLayer_ChangeInfo : CreateStructureMember_ChangeInfo
         Guid guidValue,
         bool hasMask,
         bool maskIsVisible,
-        bool lockTransparency) : base(parentGuid, index, opacity, isVisible, clipToMemberBelow, name, blendMode, guidValue, hasMask, maskIsVisible)
+        bool lockTransparency,
+        ImmutableArray<NodePropertyInfo> inputs,
+        ImmutableArray<NodePropertyInfo> outputs) : 
+        base(parentGuid, index, opacity, isVisible, clipToMemberBelow, name, blendMode, guidValue, hasMask, maskIsVisible, inputs, outputs)
     {
         LockTransparency = lockTransparency;
     }

+ 9 - 3
src/PixiEditor.ChangeableDocument/ChangeInfos/Structure/CreateStructureMember_ChangeInfo.cs

@@ -1,4 +1,8 @@
-using PixiEditor.ChangeableDocument.Enums;
+using System.Collections.Immutable;
+using System.Reflection;
+using PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
+using PixiEditor.ChangeableDocument.Enums;
+using PixiEditor.Numerics;
 
 namespace PixiEditor.ChangeableDocument.ChangeInfos.Structure;
 
@@ -12,5 +16,7 @@ public abstract record class CreateStructureMember_ChangeInfo(
     BlendMode BlendMode,
     Guid Id,
     bool HasMask,
-    bool MaskIsVisible
-) : IChangeInfo;
+    bool MaskIsVisible,
+    ImmutableArray<NodePropertyInfo> InputProperties,
+    ImmutableArray<NodePropertyInfo> OutputProperties
+) : CreateNode_ChangeInfo(Name, VecD.Zero, Id, InputProperties, OutputProperties);

+ 6 - 4
src/PixiEditor.ChangeableDocument/Changeables/Graph/InputProperty.cs

@@ -8,26 +8,28 @@ public class InputProperty : IInputProperty
     public string Name { get; }
     public object Value { get; set; }
     public Node Node { get; }
+    public Type ValueType { get; } 
     IReadOnlyNode INodeProperty.Node => Node;
     
     public IOutputProperty? Connection { get; set; }
     
-    internal InputProperty(Node node, string name, object defaultValue)
+    internal InputProperty(Node node, string name, object defaultValue, Type valueType)
     {
         Name = name;
         Value = defaultValue;
         Node = node;
+        ValueType = valueType;
     }
 
     public InputProperty Clone(Node forNode)
     {
         if(Value is ICloneable cloneable)
-            return new InputProperty(forNode, Name, cloneable.Clone());
+            return new InputProperty(forNode, Name, cloneable.Clone(), ValueType);
         
         if(!Value.GetType().IsPrimitive && Value.GetType() != typeof(string))
             throw new InvalidOperationException("Value is not cloneable and not a primitive type");
         
-        return new InputProperty(forNode, Name, Value);
+        return new InputProperty(forNode, Name, Value, ValueType);
     }
 }
 
@@ -40,7 +42,7 @@ public class InputProperty<T> : InputProperty, IInputProperty<T>
         set => base.Value = value;
     }
     
-    internal InputProperty(Node node, string name, T defaultValue) : base(node, name, defaultValue)
+    internal InputProperty(Node node, string name, T defaultValue) : base(node, name, defaultValue, typeof(T))
     {
     }
 }

+ 3 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/INodeProperty.cs

@@ -5,11 +5,14 @@ public interface INodeProperty
     public string Name { get; }
     public object Value { get; set; }
     public IReadOnlyNode Node { get; }
+    public Type ValueType { get; }
 }
 
 public interface INodeProperty<T> : INodeProperty
 {
     public new T Value { get; set; }
+
+    Type INodeProperty.ValueType => typeof(T);
 }
 
 public interface IInputProperty : INodeProperty

+ 7 - 5
src/PixiEditor.ChangeableDocument/Changeables/Graph/OutputProperty.cs

@@ -10,8 +10,10 @@ public class OutputProperty : IOutputProperty
     private List<IInputProperty> _connections = new();
     private object _value;
     public string Name { get; }
-
     public Node Node { get; }
+
+    public Type ValueType { get; }
+
     IReadOnlyNode INodeProperty.Node => Node;
 
     public object Value
@@ -32,12 +34,12 @@ public class OutputProperty : IOutputProperty
     public event InputConnectedEvent Connected;
     public event InputConnectedEvent Disconnected;
 
-    internal OutputProperty(Node node, string name, object defaultValue)
+    internal OutputProperty(Node node, string name, object defaultValue, Type valueType)
     {
         Name = name;
         Value = defaultValue;
-        _connections = new List<IInputProperty>();
         Node = node;
+        ValueType = valueType;
     }
 
     public void ConnectTo(IInputProperty property)
@@ -69,7 +71,7 @@ public class OutputProperty : IOutputProperty
      
         object value = Value is ICloneable cloneableValue ? cloneableValue.Clone() : Value;
         
-        var newOutput = new OutputProperty(clone, Name, value);
+        var newOutput = new OutputProperty(clone, Name, value, ValueType);
         foreach (var connection in Connections)
         {
             newOutput.ConnectTo(connection);
@@ -87,7 +89,7 @@ public class OutputProperty<T> : OutputProperty, INodeProperty<T>
         set => base.Value = value;
     }
 
-    internal OutputProperty(Node node, string name, T defaultValue) : base(node, name, defaultValue)
+    internal OutputProperty(Node node, string name, T defaultValue) : base(node, name, defaultValue, typeof(T))
     {
     }
 }

+ 13 - 2
src/PixiEditor.ChangeableDocument/Changes/NodeGraph/CreateNode_Change.cs

@@ -1,4 +1,6 @@
-using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using System.Collections.Immutable;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
 using PixiEditor.Numerics;
 
@@ -31,7 +33,11 @@ internal class CreateNode_Change : Change
         node.Id = id;
         target.NodeGraph.AddNode(node);
         ignoreInUndo = false;
-        return new CreateNode_ChangeInfo(nodeType.Name, node.Position, id);
+        
+        var inputInfos = CreatePropertyInfos(node.InputProperties, true);
+        var outputInfos = CreatePropertyInfos(node.OutputProperties, false);
+        
+        return new CreateNode_ChangeInfo(nodeType.Name, node.Position, id, inputInfos, outputInfos);
     }
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
@@ -41,4 +47,9 @@ internal class CreateNode_Change : Change
         
         return new DeleteNode_ChangeInfo(id);
     }
+    
+    private ImmutableArray<NodePropertyInfo> CreatePropertyInfos(IEnumerable<INodeProperty> properties, bool isInput)
+    {
+        return properties.Select(p => new NodePropertyInfo(p.Name, p.ValueType, isInput, id)).ToImmutableArray();
+    }
 }

+ 30 - 3
src/PixiEditor.ChangeableDocument/Changes/Structure/CreateStructureMember_Change.cs

@@ -1,7 +1,10 @@
-using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using System.Collections.Immutable;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
 using PixiEditor.ChangeableDocument.ChangeInfos.Structure;
 using PixiEditor.ChangeableDocument.Enums;
+using PixiEditor.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changes.Structure;
 
@@ -46,9 +49,28 @@ internal class CreateStructureMember_Change : Change
         AppendMember(backgroundInput, member);
 
         ignoreInUndo = false;
+
+        CreateNode_ChangeInfo changeInfo = new CreateNode_ChangeInfo(
+            member.MemberName, 
+            new VecD(0, 0),
+            member.Id, 
+            CreatePropertyInfos(member.InputProperties, true),
+            CreatePropertyInfos(member.OutputProperties, false));
+        
+        List<IChangeInfo> changes = new()
+        {
+            changeInfo,
+            CreateChangeInfo(member)
+        };
+
+        return changes; 
+    }
+    
+    private IChangeInfo CreateChangeInfo(StructureNode member)
+    {
         return type switch
         {
-            StructureMemberType.Layer => CreateLayer_ChangeInfo.FromLayer(parentFolderGuid, parentFolderIndex,
+             StructureMemberType.Layer => CreateLayer_ChangeInfo.FromLayer(parentFolderGuid, parentFolderIndex,
                 (LayerNode)member),
             StructureMemberType.Folder => CreateFolder_ChangeInfo.FromFolder(parentFolderGuid, parentFolderIndex,
                 (FolderNode)member),
@@ -56,6 +78,11 @@ internal class CreateStructureMember_Change : Change
         };
     }
 
+    private ImmutableArray<NodePropertyInfo> CreatePropertyInfos(IEnumerable<INodeProperty> properties, bool isInput)
+    {
+        return properties.Select(p => new NodePropertyInfo(p.Name, p.ValueType, isInput, newMemberGuid)).ToImmutableArray();
+    }
+
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document document)
     {
         var container = document.FindNodeOrThrow<Node>(parentFolderGuid);
@@ -69,7 +96,7 @@ internal class CreateStructureMember_Change : Change
         child.Dispose();
 
         document.NodeGraph.RemoveNode(child);
-        
+
         childBackgroundConnection?.ConnectTo(backgroundInput.Background);
 
         return new DeleteStructureMember_ChangeInfo(newMemberGuid, parentFolderGuid);