Browse Source

Connect works

Krzysztof Krysiński 1 year ago
parent
commit
84e4d32485

+ 2 - 0
src/PixiEditor.AvaloniaUI/Styles/Templates/NodeGraphView.axaml

@@ -47,6 +47,8 @@
                                         DragCommand="{Binding DraggedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"
                                         EndDragCommand="{Binding EndDragCommand,
                                         RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"
+                                        SocketDropCommand="{Binding SocketDropCommand,
+                                        RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"
                                         ResultPreview="{Binding ResultPreview}" />
                                 </DataTemplate>
                             </ItemsControl.ItemTemplate>

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

@@ -9,6 +9,7 @@
                 <Grid Margin="-10, 0">
                     <Grid.ColumnDefinitions>10*, *, 10*</Grid.ColumnDefinitions>
                     <properties:NodeSocket Name="PART_InputSocket"
+                                           Node="{Binding DataContext.Node, RelativeSource={RelativeSource TemplatedParent}}"
                                            Label="{Binding DataContext.DisplayName, RelativeSource={RelativeSource TemplatedParent}}"
                                            IsVisible="{Binding DataContext.IsInput, 
                     RelativeSource={RelativeSource TemplatedParent}}">

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

@@ -114,4 +114,14 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler
     {
         Internals.ActionAccumulator.AddFinishedActions(new CreateNode_Action(nodeType, Guid.NewGuid()));
     }
+
+    public void ConnectProperties(INodePropertyHandler start, INodePropertyHandler end)
+    {
+        INodeHandler inputNode = start.IsInput ? start.Node : end.Node;
+        INodeHandler outputNode = start.IsInput ? end.Node : start.Node;
+        string inputProperty = start.IsInput ? start.PropertyName : end.PropertyName;
+        string outputProperty = start.IsInput ? end.PropertyName : start.PropertyName;
+
+        Internals.ActionAccumulator.AddFinishedActions(new ConnectProperties_Action(inputNode.Id, outputNode.Id, inputProperty, outputProperty));
+    }
 }

+ 6 - 0
src/PixiEditor.AvaloniaUI/ViewModels/SubViewModels/NodeGraphManagerViewModel.cs

@@ -16,6 +16,12 @@ internal class NodeGraphManagerViewModel : SubViewModel<ViewModelMain>
         Owner.DocumentManagerSubViewModel.ActiveDocument?.NodeGraph.CreateNode(nodeType);
     }
 
+    [Command.Internal("PixiEditor.NodeGraph.ConnectProperties")]
+    public void ConnectProperties((INodePropertyHandler input, INodePropertyHandler output) args)
+    {
+        Owner.DocumentManagerSubViewModel.ActiveDocument?.NodeGraph.ConnectProperties(args.input, args.output);
+    }
+
     [Command.Internal("PixiEditor.NodeGraph.ChangeNodePos")]
     public void ChangeNodePos((INodeHandler node, VecD newPos) args)
     {

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

@@ -12,6 +12,7 @@
         <dock:NodeGraphDockViewModel/>
     </Design.DataContext>
     <nodes:NodeGraphView
+        ConnectPropertiesCommand="{xaml:Command PixiEditor.NodeGraph.ConnectProperties, UseProvided=True}"
         CreateNodeCommand="{xaml:Command PixiEditor.NodeGraph.CreateNode, UseProvided=True}"
         ChangeNodePosCommand="{xaml:Command PixiEditor.NodeGraph.ChangeNodePos, UseProvided=True}"
         EndChangeNodePosCommand="{xaml:Command PixiEditor.NodeGraph.EndChangeNodePos, UseProvided=True}"

+ 69 - 8
src/PixiEditor.AvaloniaUI/Views/Nodes/NodeGraphView.cs

@@ -11,6 +11,7 @@ using PixiEditor.AvaloniaUI.Helpers;
 using PixiEditor.AvaloniaUI.Models.Handlers;
 using PixiEditor.AvaloniaUI.ViewModels.Document;
 using PixiEditor.AvaloniaUI.ViewModels.Nodes;
+using PixiEditor.AvaloniaUI.Views.Nodes.Properties;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.Numerics;
 using Point = Avalonia.Point;
@@ -51,9 +52,22 @@ internal class NodeGraphView : Zoombox.Zoombox
         nameof(SearchQuery));
 
     public static readonly StyledProperty<ObservableCollection<Type>> AllNodeTypesProperty = AvaloniaProperty.Register<NodeGraphView, ObservableCollection<Type>>(
-        "AllNodeTypes");
+        nameof(AllNodeTypes));
+
+    public static readonly StyledProperty<ICommand> SocketDropCommandProperty = AvaloniaProperty.Register<NodeGraphView, ICommand>(
+        nameof(SocketDropCommand));
 
     public static readonly StyledProperty<ICommand> CreateNodeCommandProperty = AvaloniaProperty.Register<NodeGraphView, ICommand>("CreateNodeCommand");
+
+    public static readonly StyledProperty<ICommand> ConnectPropertiesCommandProperty = AvaloniaProperty.Register<NodeGraphView, ICommand>(
+        "ConnectPropertiesCommand");
+
+    public ICommand ConnectPropertiesCommand
+    {
+        get => GetValue(ConnectPropertiesCommandProperty);
+        set => SetValue(ConnectPropertiesCommandProperty, value);
+    }
+
     public ObservableCollection<Type> AllNodeTypes
     {
         get => GetValue(AllNodeTypesProperty);
@@ -120,10 +134,21 @@ internal class NodeGraphView : Zoombox.Zoombox
         set { SetValue(CreateNodeCommandProperty, value); }
     }
 
+    public ICommand SocketDropCommand
+    {
+        get => GetValue(SocketDropCommandProperty);
+        set => SetValue(SocketDropCommandProperty, value);
+    }
+
     private bool isDraggingNodes;
+    private bool isDraggingConnection;
     private VecD clickPointOffset;
 
     private List<VecD> initialNodePositions;
+    private INodePropertyHandler startConnectionProperty;
+    private INodePropertyHandler endConnectionProperty;
+    private INodeHandler startConnectionNode;
+    private INodeHandler endConnectionNode;
 
     public NodeGraphView()
     {
@@ -131,6 +156,7 @@ internal class NodeGraphView : Zoombox.Zoombox
         StartDraggingCommand = new RelayCommand<PointerPressedEventArgs>(StartDragging);
         DraggedCommand = new RelayCommand<PointerEventArgs>(Dragged);
         EndDragCommand = new RelayCommand<PointerCaptureLostEventArgs>(EndDrag);
+        SocketDropCommand = new RelayCommand<NodeSocket>(SocketDrop);
 
         AllNodeTypes = new ObservableCollection<Type>(GatherAssemblyTypes<Node>());
     }
@@ -156,10 +182,19 @@ internal class NodeGraphView : Zoombox.Zoombox
     {
         if (e.GetMouseButton(this) == MouseButton.Left)
         {
-            isDraggingNodes = true;
-            Point pt = e.GetPosition(this);
-            clickPointOffset = ToZoomboxSpace(new VecD(pt.X, pt.Y));
-            initialNodePositions = SelectedNodes.Select(x => x.PositionBindable).ToList();
+            if (e.Source is NodeSocket nodeSocket)
+            {
+                startConnectionProperty = nodeSocket.Property;
+                startConnectionNode = nodeSocket.Node;
+                isDraggingConnection = true;
+            }
+            else
+            {
+                isDraggingNodes = true;
+                Point pt = e.GetPosition(this);
+                clickPointOffset = ToZoomboxSpace(new VecD(pt.X, pt.Y));
+                initialNodePositions = SelectedNodes.Select(x => x.PositionBindable).ToList();
+            }
         }
     }
 
@@ -179,13 +214,39 @@ internal class NodeGraphView : Zoombox.Zoombox
 
     private void EndDrag(PointerCaptureLostEventArgs e)
     {
-        isDraggingNodes = false;
-        EndChangeNodePosCommand?.Execute(null);
+        if (isDraggingNodes)
+        {
+            isDraggingNodes = false;
+            EndChangeNodePosCommand?.Execute(null);
+        }
+    }
+
+    private void SocketDrop(NodeSocket socket)
+    {
+        endConnectionNode = socket.Node;
+        endConnectionProperty = socket.Property;
+
+        if (startConnectionNode == null || endConnectionNode == null || startConnectionProperty == null || endConnectionProperty == null)
+        {
+            return;
+        }
+
+        var connection = (startConnectionProperty, endConnectionProperty);
+
+        if (startConnectionNode == endConnectionNode)
+        {
+            return;
+        }
+
+        if(ConnectPropertiesCommand != null && ConnectPropertiesCommand.CanExecute(connection))
+        {
+            ConnectPropertiesCommand.Execute(connection);
+        }
     }
 
     private void SelectNode(PointerPressedEventArgs e)
     {
-        NodeViewModel viewModel = (NodeViewModel)e.Source;
+        if (e.Source is not NodeViewModel viewModel) return;
 
         if (e.KeyModifiers.HasFlag(KeyModifiers.Control))
         {

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

@@ -103,6 +103,17 @@ public class NodeView : TemplatedControl
         set { SetValue(EndDragCommandProperty, value); }
     }
 
+    public static readonly StyledProperty<ICommand> SocketDropCommandProperty = AvaloniaProperty.Register<NodeView, ICommand>(
+        nameof(SocketDropCommand));
+
+    public ICommand SocketDropCommand
+    {
+        get => GetValue(SocketDropCommandProperty);
+        set => SetValue(SocketDropCommandProperty, value);
+    }
+
+    private bool captured;
+
     static NodeView()
     {
         IsSelectedProperty.Changed.Subscribe(NodeSelectionChanged);
@@ -115,7 +126,7 @@ public class NodeView : TemplatedControl
             return;
         
         var originalSource = e.Source;
-        e.Source = Node; 
+        e.Source = e.Source is NodeSocket socket ? socket : Node;
         if (SelectNodeCommand != null && SelectNodeCommand.CanExecute(e))
         {
             SelectNodeCommand.Execute(e);
@@ -123,7 +134,12 @@ public class NodeView : TemplatedControl
         
         if(StartDragCommand != null && StartDragCommand.CanExecute(e))
         {
-            e.Pointer.Capture(this);
+            if (e.Source is not NodeSocket)
+            {
+                e.Pointer.Capture(this);
+                captured = true;
+            }
+
             StartDragCommand.Execute(e);
         }
         
@@ -134,25 +150,40 @@ public class NodeView : TemplatedControl
     protected override void OnPointerMoved(PointerEventArgs e)
     {
         base.OnPointerMoved(e);
-        if(e.Pointer.Captured != this)
+
+        if(!Equals(e.Pointer.Captured, this) && e.Source is not NodeSocket socket)
             return;
-        
+
         if (DragCommand != null && DragCommand.CanExecute(e))
         {
             DragCommand.Execute(e);
         }
     }
 
+    protected override void OnPointerReleased(PointerReleasedEventArgs e)
+    {
+        if (e.Source is NodeSocket socket)
+        {
+            if (SocketDropCommand != null && SocketDropCommand.CanExecute(socket))
+            {
+                SocketDropCommand?.Execute(socket);
+            }
+        }
+    }
+
     protected override void OnPointerCaptureLost(PointerCaptureLostEventArgs e)
     {
+        if(!captured) return;
+
         var originalSource = e.Source;
-        e.Source = Node; 
+        e.Source = Node;
         if (EndDragCommand != null && EndDragCommand.CanExecute(e))
         {
             EndDragCommand.Execute(e);
         }
         
         e.Source = originalSource;
+        captured = false;
         e.Handled = true;
     }
 

+ 8 - 0
src/PixiEditor.AvaloniaUI/Views/Nodes/Properties/NodePropertyView.cs

@@ -1,6 +1,7 @@
 using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Controls.Primitives;
+using PixiEditor.AvaloniaUI.Models.Handlers;
 using PixiEditor.AvaloniaUI.ViewModels.Nodes;
 
 namespace PixiEditor.AvaloniaUI.Views.Nodes.Properties;
@@ -24,6 +25,13 @@ public abstract class NodePropertyView : UserControl
         base.OnApplyTemplate(e);
         InputSocket = e.NameScope.Find<NodeSocket>("PART_InputSocket");
         OutputSocket = e.NameScope.Find<NodeSocket>("PART_OutputSocket");
+
+        INodePropertyHandler propertyHandler = DataContext as INodePropertyHandler;
+
+        InputSocket.Property = propertyHandler;
+        InputSocket.Node = propertyHandler.Node;
+        OutputSocket.Node = propertyHandler.Node;
+        OutputSocket.Property = propertyHandler;
     }
 
     public Point GetSocketPoint(bool getInputSocket, Canvas canvas)

+ 28 - 0
src/PixiEditor.AvaloniaUI/Views/Nodes/Properties/NodeSocket.cs

@@ -1,6 +1,9 @@
 using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Controls.Primitives;
+using Avalonia.Input;
+using PixiEditor.AvaloniaUI.Helpers;
+using PixiEditor.AvaloniaUI.Models.Handlers;
 
 namespace PixiEditor.AvaloniaUI.Views.Nodes.Properties;
 
@@ -9,6 +12,15 @@ 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 static readonly StyledProperty<INodeHandler> NodeProperty = AvaloniaProperty.Register<NodeSocket, INodeHandler>(
+        "Node");
+
+    public INodeHandler Node
+    {
+        get => GetValue(NodeProperty);
+        set => SetValue(NodeProperty, value);
+    }
+
     public bool IsInput
     {
         get { return (bool)GetValue(IsInputProperty); }
@@ -23,10 +35,26 @@ public class NodeSocket : TemplatedControl
     
     public Control ConnectPort { get; set; }
 
+    public INodePropertyHandler Property { get; set; }
+
     protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
     {
         base.OnApplyTemplate(e);
         ConnectPort = e.NameScope.Find<Control>("PART_ConnectPort");
+        ConnectPort.PointerPressed += ConnectPortOnPointerPressed;
+        ConnectPort.PointerReleased += ConnectPortOnPointerReleased;
+    }
+
+    private void ConnectPortOnPointerPressed(object? sender, PointerPressedEventArgs e)
+    {
+        e.Source = this;
+        e.Pointer.Capture(null);
+    }
+
+    private void ConnectPortOnPointerReleased(object? sender, PointerEventArgs e)
+    {
+        e.Source = this;
+        e.Pointer.Capture(null);
     }
 }
 

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

@@ -171,4 +171,14 @@ public abstract class Node(Guid? id = null) : IReadOnlyNode, IDisposable
         }
         return clone;
     }
+
+    public InputProperty? GetInputProperty(string inputProperty)
+    {
+        return inputs.FirstOrDefault(x => x.InternalPropertyName == inputProperty);
+    }
+
+    public OutputProperty? GetOutputProperty(string outputProperty)
+    {
+        return outputs.FirstOrDefault(x => x.InternalPropertyName == outputProperty);
+    }
 }

+ 78 - 0
src/PixiEditor.ChangeableDocument/Changes/NodeGraph/ConnectProperties_Change.cs

@@ -0,0 +1,78 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
+
+namespace PixiEditor.ChangeableDocument.Changes.NodeGraph;
+
+internal class ConnectProperties_Change : Change
+{
+    public Guid InputNodeId { get; }
+    public Guid OutputNodeId { get; }
+    public string InputProperty { get; }
+    public string OutputProperty { get; }
+
+    private IOutputProperty? originalConnection;
+
+    [GenerateMakeChangeAction]
+    public ConnectProperties_Change(Guid inputNodeId, Guid outputNodeId, string inputProperty, string outputProperty)
+    {
+        InputNodeId = inputNodeId;
+        OutputNodeId = outputNodeId;
+        InputProperty = inputProperty;
+        OutputProperty = outputProperty;
+    }
+
+    public override bool InitializeAndValidate(Document target)
+    {
+        Node inputNode = target.FindNode(InputNodeId);
+        Node outputNode = target.FindNode(OutputNodeId);
+
+        if (inputNode == null || outputNode == null)
+        {
+            return false;
+        }
+
+        InputProperty? inputProp = inputNode.GetInputProperty(InputProperty);
+        OutputProperty? outputProp = outputNode.GetOutputProperty(OutputProperty);
+
+        if (inputProp == null || outputProp == null)
+        {
+            return false;
+        }
+
+        originalConnection = inputProp.Connection;
+
+        return true;
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
+    {
+        Node inputNode = target.FindNode(InputNodeId);
+        Node outputNode = target.FindNode(OutputNodeId);
+
+        InputProperty inputProp = inputNode.GetInputProperty(InputProperty);
+        OutputProperty outputProp = outputNode.GetOutputProperty(OutputProperty);
+
+        outputProp.ConnectTo(inputProp);
+
+        ignoreInUndo = false;
+
+        return new ConnectProperty_ChangeInfo(outputNode.Id, inputNode.Id, OutputProperty, InputProperty);
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
+    {
+        Node inputNode = target.FindNode(InputNodeId);
+        Node outputNode = target.FindNode(OutputNodeId);
+
+        InputProperty inputProp = inputNode.GetInputProperty(InputProperty);
+        OutputProperty outputProp = outputNode.GetOutputProperty(OutputProperty);
+
+        outputProp.DisconnectFrom(inputProp);
+        inputProp.Connection = originalConnection;
+
+
+        return new ConnectProperty_ChangeInfo(outputNode.Id, inputNode.Id, OutputProperty, InputProperty);
+    }
+}