Browse Source

Added disconnect property

flabbet 1 year ago
parent
commit
e0bd262b4b

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

@@ -538,7 +538,7 @@ internal class DocumentUpdater
         }
         else
         {
-            doc.NodeGraphHandler.RemoveConnection(info.InputNodeId, info.OutputProperty);
+            doc.NodeGraphHandler.RemoveConnection(info.InputNodeId, info.InputProperty);
         }
     }
     

+ 83 - 53
src/PixiEditor.AvaloniaUI/ViewModels/Document/NodeGraphViewModel.cs

@@ -2,6 +2,7 @@
 using PixiEditor.AvaloniaUI.Models.DocumentModels;
 using PixiEditor.AvaloniaUI.Models.Handlers;
 using PixiEditor.AvaloniaUI.ViewModels.Nodes;
+using PixiEditor.ChangeableDocument.Actions;
 using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.Numerics;
@@ -15,7 +16,7 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler
     public ObservableCollection<NodeConnectionViewModel> Connections { get; } = new();
     public StructureTree StructureTree { get; } = new();
     public INodeHandler? OutputNode { get; private set; }
-    
+
     private DocumentInternalParts Internals { get; }
 
     public NodeGraphViewModel(DocumentViewModel documentViewModel, DocumentInternalParts internals)
@@ -26,24 +27,24 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler
 
     public void AddNode(INodeHandler node)
     {
-        if(OutputNode == null)
+        if (OutputNode == null)
         {
             OutputNode = node; // TODO: this is not really correct yet, a way to check what node type is added is needed
         }
-        
+
         AllNodes.Add(node);
         StructureTree.Update(this);
     }
-    
+
     public void RemoveNode(Guid nodeId)
     {
         var node = AllNodes.FirstOrDefault(x => x.Id == nodeId);
-        
+
         if (node != null)
         {
             AllNodes.Remove(node);
         }
-        
+
         StructureTree.Update(this);
     }
 
@@ -56,38 +57,40 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler
             existingInputConnection.InputProperty.ConnectedOutput = null;
             existingInputConnection.OutputProperty.ConnectedInputs.Remove(existingInputConnection.InputProperty);
         }
-        
+
         connection.InputProperty.ConnectedOutput = connection.OutputProperty;
         connection.OutputProperty.ConnectedInputs.Add(connection.InputProperty);
-        
+
         Connections.Add(connection);
-        
+
         StructureTree.Update(this);
     }
 
     public void RemoveConnection(Guid nodeId, string property)
     {
-        var connection = Connections.FirstOrDefault(x => x.InputProperty.Node.Id == nodeId && x.InputProperty.PropertyName == property);
+        var connection = Connections.FirstOrDefault(x =>
+            x.InputProperty.Node.Id == nodeId && x.InputProperty.PropertyName == property);
         if (connection != null)
         {
             connection.InputProperty.ConnectedOutput = null;
             connection.OutputProperty.ConnectedInputs.Remove(connection.InputProperty);
             Connections.Remove(connection);
         }
-        
+
         StructureTree.Update(this);
     }
-    
+
     public void RemoveConnections(Guid nodeId)
     {
-        var connections = Connections.Where(x => x.InputProperty.Node.Id == nodeId || x.OutputProperty.Node.Id == nodeId).ToList();
+        var connections = Connections
+            .Where(x => x.InputProperty.Node.Id == nodeId || x.OutputProperty.Node.Id == nodeId).ToList();
         foreach (var connection in connections)
         {
             connection.InputProperty.ConnectedOutput = null;
             connection.OutputProperty.ConnectedInputs.Remove(connection.InputProperty);
             Connections.Remove(connection);
         }
-        
+
         StructureTree.Update(this);
     }
 
@@ -105,45 +108,45 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler
 
         return true;
     }
-    
+
     private Queue<INodeHandler> CalculateExecutionQueue(INodeHandler outputNode)
-       {
-           // backwards breadth-first search
-           var visited = new HashSet<INodeHandler>();
-           var queueNodes = new Queue<INodeHandler>();
-           List<INodeHandler> finalQueue = new();
-           queueNodes.Enqueue(outputNode);
-   
-           while (queueNodes.Count > 0)
-           {
-               var node = queueNodes.Dequeue();
-               if (!visited.Add(node))
-               {
-                   continue;
-               }
-   
-               finalQueue.Add(node);
-   
-               foreach (var input in node.Inputs)
-               {
-                   if (input.ConnectedOutput == null)
-                   {
-                       continue;
-                   }
-   
-                   queueNodes.Enqueue(input.ConnectedOutput.Node);
-               }
-           }
-   
-           finalQueue.Reverse();
-           return new Queue<INodeHandler>(finalQueue);
-       }
+    {
+        // backwards breadth-first search
+        var visited = new HashSet<INodeHandler>();
+        var queueNodes = new Queue<INodeHandler>();
+        List<INodeHandler> finalQueue = new();
+        queueNodes.Enqueue(outputNode);
+
+        while (queueNodes.Count > 0)
+        {
+            var node = queueNodes.Dequeue();
+            if (!visited.Add(node))
+            {
+                continue;
+            }
+
+            finalQueue.Add(node);
+
+            foreach (var input in node.Inputs)
+            {
+                if (input.ConnectedOutput == null)
+                {
+                    continue;
+                }
+
+                queueNodes.Enqueue(input.ConnectedOutput.Node);
+            }
+        }
+
+        finalQueue.Reverse();
+        return new Queue<INodeHandler>(finalQueue);
+    }
 
     public void SetNodePosition(INodeHandler node, VecD newPos)
     {
         Internals.ActionAccumulator.AddActions(new NodePosition_Action(node.Id, newPos));
     }
-    
+
     public void EndChangeNodePosition()
     {
         Internals.ActionAccumulator.AddFinishedActions(new EndNodePosition_Action());
@@ -154,13 +157,40 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler
         Internals.ActionAccumulator.AddFinishedActions(new CreateNode_Action(nodeType, Guid.NewGuid()));
     }
 
-    public void ConnectProperties(INodePropertyHandler start, INodePropertyHandler end)
+    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;
+        if (start == null && end == null) return;
+
+        INodeHandler inputNode = null, outputNode = null;
+        string inputProperty = null, outputProperty = null;
 
-        Internals.ActionAccumulator.AddFinishedActions(new ConnectProperties_Action(inputNode.Id, outputNode.Id, inputProperty, outputProperty));
+        var input = start?.IsInput == true ? start : end;
+        var output = start?.IsInput == false ? start : end;
+        
+        if (input == null && output != null)
+        {
+            input = output.ConnectedInputs.FirstOrDefault();
+            output = null;
+        }
+
+        if (input != null)
+        {
+            inputNode = input.Node;
+            inputProperty = input.PropertyName;
+        }
+
+        if (output != null)
+        {
+            outputNode = output.Node;
+            outputProperty = output.PropertyName;
+        }
+
+        if (input == null) return;
+
+        IAction action = input != null && output != null ?
+            new ConnectProperties_Action(inputNode.Id, outputNode.Id, inputProperty, outputProperty) :
+            new DisconnectProperty_Action(inputNode.Id, inputProperty);
+        
+        Internals.ActionAccumulator.AddFinishedActions(action);
     }
 }

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

@@ -171,6 +171,15 @@ internal class NodeGraphView : Zoombox.Zoombox
         }
     }
 
+    protected override void OnPointerReleased(PointerReleasedEventArgs e)
+    {
+        base.OnPointerReleased(e);
+        if (startConnectionProperty != null)
+        {
+            SocketDrop(null);
+        }
+    }
+
     private IEnumerable<Type> GatherAssemblyTypes<T>()
     {
         return AppDomain.CurrentDomain.GetAssemblies()
@@ -223,25 +232,40 @@ internal class NodeGraphView : Zoombox.Zoombox
 
     private void SocketDrop(NodeSocket socket)
     {
-        endConnectionNode = socket.Node;
-        endConnectionProperty = socket.Property;
-
-        if (startConnectionNode == null || endConnectionNode == null || startConnectionProperty == null || endConnectionProperty == null)
+        if(startConnectionProperty == null)
         {
             return;
         }
+        
+        (INodePropertyHandler, INodePropertyHandler) connection = (startConnectionProperty, null);
+        if (socket != null)
+        {
+            endConnectionNode = socket.Node;
+            endConnectionProperty = socket.Property;
 
-        var connection = (startConnectionProperty, endConnectionProperty);
+            if (startConnectionNode == null || endConnectionNode == null || startConnectionProperty == null ||
+                endConnectionProperty == null)
+            {
+                return;
+            }
 
-        if (startConnectionNode == endConnectionNode)
-        {
-            return;
+            connection = (startConnectionProperty, endConnectionProperty);
+
+            if (startConnectionNode == endConnectionNode)
+            {
+                return;
+            }
         }
 
         if(ConnectPropertiesCommand != null && ConnectPropertiesCommand.CanExecute(connection))
         {
             ConnectPropertiesCommand.Execute(connection);
         }
+        
+        startConnectionProperty = null;
+        endConnectionProperty = null;
+        startConnectionNode = null;
+        endConnectionNode = null;
     }
 
     private void SelectNode(PointerPressedEventArgs e)

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

@@ -18,9 +18,10 @@ namespace PixiEditor.AvaloniaUI.Views.Nodes;
 [PseudoClasses(":selected")]
 public class NodeView : TemplatedControl
 {
-    public static readonly StyledProperty<INodeHandler> NodeProperty = AvaloniaProperty.Register<NodeView, INodeHandler>(
-        nameof(Node));
-    
+    public static readonly StyledProperty<INodeHandler> NodeProperty =
+        AvaloniaProperty.Register<NodeView, INodeHandler>(
+            nameof(Node));
+
     public static readonly StyledProperty<string> DisplayNameProperty = AvaloniaProperty.Register<NodeView, string>(
         nameof(DisplayName), "Node");
 
@@ -38,17 +39,24 @@ public class NodeView : TemplatedControl
     public static readonly StyledProperty<bool> IsSelectedProperty = AvaloniaProperty.Register<NodeView, bool>(
         nameof(IsSelected));
 
-    public static readonly StyledProperty<ICommand> SelectNodeCommandProperty = AvaloniaProperty.Register<NodeView, ICommand>("SelectNodeCommand");
-    public static readonly StyledProperty<ICommand> StartDragCommandProperty = AvaloniaProperty.Register<NodeView, ICommand>("StartDragCommand");
-    public static readonly StyledProperty<ICommand> DragCommandProperty = AvaloniaProperty.Register<NodeView, ICommand>("DragCommand");
-    public static readonly StyledProperty<ICommand> EndDragCommandProperty = AvaloniaProperty.Register<NodeView, ICommand>("EndDragCommand");
+    public static readonly StyledProperty<ICommand> SelectNodeCommandProperty =
+        AvaloniaProperty.Register<NodeView, ICommand>("SelectNodeCommand");
+
+    public static readonly StyledProperty<ICommand> StartDragCommandProperty =
+        AvaloniaProperty.Register<NodeView, ICommand>("StartDragCommand");
+
+    public static readonly StyledProperty<ICommand> DragCommandProperty =
+        AvaloniaProperty.Register<NodeView, ICommand>("DragCommand");
+
+    public static readonly StyledProperty<ICommand> EndDragCommandProperty =
+        AvaloniaProperty.Register<NodeView, ICommand>("EndDragCommand");
 
     public INodeHandler Node
     {
         get => GetValue(NodeProperty);
         set => SetValue(NodeProperty, value);
     }
-    
+
     public bool IsSelected
     {
         get => GetValue(IsSelectedProperty);
@@ -103,8 +111,9 @@ public class NodeView : TemplatedControl
         set { SetValue(EndDragCommandProperty, value); }
     }
 
-    public static readonly StyledProperty<ICommand> SocketDropCommandProperty = AvaloniaProperty.Register<NodeView, ICommand>(
-        nameof(SocketDropCommand));
+    public static readonly StyledProperty<ICommand> SocketDropCommandProperty =
+        AvaloniaProperty.Register<NodeView, ICommand>(
+            nameof(SocketDropCommand));
 
     public ICommand SocketDropCommand
     {
@@ -122,17 +131,17 @@ public class NodeView : TemplatedControl
     protected override void OnPointerPressed(PointerPressedEventArgs e)
     {
         base.OnPointerPressed(e);
-        if(e.GetMouseButton(this) != MouseButton.Left)
+        if (e.GetMouseButton(this) != MouseButton.Left)
             return;
-        
+
         var originalSource = e.Source;
         e.Source = e.Source is NodeSocket socket ? socket : Node;
         if (SelectNodeCommand != null && SelectNodeCommand.CanExecute(e))
         {
             SelectNodeCommand.Execute(e);
         }
-        
-        if(StartDragCommand != null && StartDragCommand.CanExecute(e))
+
+        if (StartDragCommand != null && StartDragCommand.CanExecute(e))
         {
             if (e.Source is not NodeSocket)
             {
@@ -142,16 +151,16 @@ public class NodeView : TemplatedControl
 
             StartDragCommand.Execute(e);
         }
-        
+
         e.Source = originalSource;
         e.Handled = true;
     }
-    
+
     protected override void OnPointerMoved(PointerEventArgs e)
     {
         base.OnPointerMoved(e);
 
-        if(!Equals(e.Pointer.Captured, this) && e.Source is not NodeSocket socket)
+        if (!Equals(e.Pointer.Captured, this) && e.Source is not NodeSocket socket)
             return;
 
         if (DragCommand != null && DragCommand.CanExecute(e))
@@ -162,18 +171,21 @@ public class NodeView : TemplatedControl
 
     protected override void OnPointerReleased(PointerReleasedEventArgs e)
     {
-        if (e.Source is NodeSocket socket)
+        NodeSocket socket = null;
+        if (e.Source is NodeSocket sourceSocket)
         {
-            if (SocketDropCommand != null && SocketDropCommand.CanExecute(socket))
-            {
-                SocketDropCommand?.Execute(socket);
-            }
+            socket = sourceSocket;
+        }
+
+        if (SocketDropCommand != null && SocketDropCommand.CanExecute(socket))
+        {
+            SocketDropCommand?.Execute(socket);
         }
     }
 
     protected override void OnPointerCaptureLost(PointerCaptureLostEventArgs e)
     {
-        if(!captured) return;
+        if (!captured) return;
 
         var originalSource = e.Source;
         e.Source = Node;
@@ -181,7 +193,7 @@ public class NodeView : TemplatedControl
         {
             EndDragCommand.Execute(e);
         }
-        
+
         e.Source = originalSource;
         captured = false;
         e.Handled = true;

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

@@ -34,9 +34,16 @@ public class MergeNode : Node
         }
         
         VecI size = Top.Value?.CommittedSize ?? Bottom.Value.CommittedSize;
-        
-        Output.Value = new ChunkyImage(size);
-        
+
+        if (Output.Value == null || Output.Value.LatestSize != size)
+        {
+            Output.Value = new ChunkyImage(size);
+        }
+        else
+        {
+            Output.Value.EnqueueClear();
+        }
+
         if (Bottom.Value != null)
         {
             Output.Value.EnqueueDrawUpToDateChunkyImage(VecI.Zero, Bottom.Value);

+ 57 - 0
src/PixiEditor.ChangeableDocument/Changes/NodeGraph/DisconnectProperty_Change.cs

@@ -0,0 +1,57 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
+
+namespace PixiEditor.ChangeableDocument.Changes.NodeGraph;
+
+internal class DisconnectProperty_Change : Change
+{
+    private Guid nodeGuid;
+    private string property;
+    
+    private IOutputProperty? originalConnection;
+
+    [GenerateMakeChangeAction]
+    public DisconnectProperty_Change(Guid nodeGuid, string inputProperty)
+    {
+        this.nodeGuid = nodeGuid;
+        this.property = inputProperty;
+    }
+
+    public override bool InitializeAndValidate(Document target)
+    {
+        if (!target.TryFindNode<Node>(nodeGuid, out var node))
+            return false;
+        
+        var inputProp = node.GetInputProperty(property) != null;
+        if (!inputProp)
+            return false;
+        
+        originalConnection = node.GetInputProperty(property).Connection;
+        return true;
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply,
+        out bool ignoreInUndo)
+    {
+        var node = target.FindNodeOrThrow<Node>(nodeGuid);
+        var input = node.GetInputProperty(property);
+        input.Connection.DisconnectFrom(input);
+
+        ignoreInUndo = false;
+        return new ConnectProperty_ChangeInfo(null, node.Id, null, property);
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
+    {
+        var node = target.FindNodeOrThrow<Node>(nodeGuid);
+        var input = node.GetInputProperty(property);
+        
+        var targetNode = target.FindNodeOrThrow<Node>(originalConnection!.Node.Id);
+        var targetOutput = targetNode.GetOutputProperty(originalConnection.InternalPropertyName);
+        
+        targetOutput.ConnectTo(input);
+
+        return new ConnectProperty_ChangeInfo(targetNode.Id, node.Id, targetOutput.InternalPropertyName, property);
+    }
+}