2
0
flabbet 7 сар өмнө
parent
commit
8d64ff79b3

+ 11 - 1
src/PixiEditor.ChangeableDocument/Changes/NodeGraph/NodePosition_UpdateableChange.cs

@@ -64,9 +64,11 @@ internal class NodePosition_UpdateableChange : UpdateableChange
         ignoreInUndo = false;
       
         VecD delta = NewPosition - startPosition;
+        bool setPosDirectly = false;
         if (NewPosition == startPosition)
         {
             delta = NewPosition;
+            setPosDirectly = true;
         }
             
         List<IChangeInfo> changes = new();
@@ -74,7 +76,15 @@ internal class NodePosition_UpdateableChange : UpdateableChange
         foreach (var nodeId in NodeIds)
         {
             var node = target.FindNode<Node>(nodeId);
-            node.Position = originalPositions[nodeId] + delta;
+            if (setPosDirectly)
+            {
+                node.Position = NewPosition;   
+            }
+            else
+            {
+                node.Position = originalPositions[nodeId] + delta;
+            }
+
             changes.Add(new NodePosition_ChangeInfo(nodeId, node.Position));
         }
         

+ 280 - 0
src/PixiEditor/Views/Nodes/LayoutAligner.cs

@@ -0,0 +1,280 @@
+using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.Models.Handlers;
+
+namespace PixiEditor.Views.Nodes;
+
+internal class LayoutAligner
+{
+    public double VerticalSpacing { get; set; } = 50;
+    public double HorizontalSpacing { get; set; } = 50;
+    public INodeGraphHandler NodeGraph { get; set; }
+
+    public Dictionary<INodeHandler, VecD> NodeSizes { get; set; }
+
+    public LayoutGroup RootGroup { get; set; }
+
+    Dictionary<INodeHandler, RectD> boundsMap = new();
+
+    public LayoutAligner(INodeGraphHandler nodeGraph, Dictionary<INodeHandler, RectD> nodeSizes)
+    {
+        NodeGraph = nodeGraph;
+        NodeSizes = nodeSizes.ToDictionary(x => x.Key, x => x.Value.Size);
+    }
+
+    public void SplitToGroups()
+    {
+        RootGroup = SplitToGroups(NodeGraph.OutputNode, NodeGraph.OutputNode);
+    }
+
+    public void Align()
+    {
+        var executionQueue = CalculateExecutionQueue(RootGroup);
+        while (executionQueue.Count > 0)
+        {
+            var group = executionQueue.Dequeue();
+            if (group.Children == null)
+            {
+                AlignNodes(group.Nodes, group.SourceNode);
+            }
+            else
+            {
+                //AlignGroup(group);
+            }
+        }
+    }
+
+    private void AlignNodes(List<INodeHandler> nodes, INodeHandler? sourceNode)
+    {
+        if (nodes.Count == 0) return;
+        foreach (var node in NodeGraph.AllNodes)
+        {
+            boundsMap[node] = new RectD(sourceNode?.PositionBindable ?? VecD.Zero, NodeSizes[node]);
+        }
+
+        nodes[0].TraverseBackwards((node, previousNode, previousConnection) =>
+        {
+            if (previousNode == null)
+            {
+                if (sourceNode == null) return true;
+
+                boundsMap[node] =
+                    new RectD(boundsMap[node].TopLeft - new VecD(boundsMap[node].Size.X + HorizontalSpacing, 0),
+                        boundsMap[node].Size);
+                node.PositionBindable = boundsMap[node].TopLeft;
+                return true;
+            }
+
+            RectD previousBounds = boundsMap[previousNode];
+            RectD bounds = boundsMap[node];
+
+            int verticalCount = previousNode.Inputs.Count(x => x.ConnectedOutput != null);
+            double totalHeightRequired = 0;
+            double startY = 0;
+
+            int indexOfVertical = 0;
+            bool found = false;
+            foreach (var input in previousNode.Inputs)
+            {
+                if (input.ConnectedOutput == null) continue;
+
+                if (input != previousConnection && !found)
+                {
+                    indexOfVertical++;
+                }
+                else if (!found)
+                {
+                    startY = totalHeightRequired;
+                    found = true;
+                }
+
+                totalHeightRequired += boundsMap[input.ConnectedOutput.Node].Height;
+            }
+
+            double shift = -totalHeightRequired / 2f + bounds.Height / 2f;
+            double moveYby = shift + startY + VerticalSpacing * indexOfVertical;
+
+            double x = previousBounds.X - previousBounds.Width - HorizontalSpacing;
+            double y = verticalCount == 1 ? 0 : previousBounds.Y + moveYby;
+
+            bounds = new RectD(x, y, bounds.Width, bounds.Height);
+
+            node.PositionBindable = bounds.TopLeft;
+            boundsMap[node] = bounds;
+            return true;
+        });
+    }
+
+    private void AlignGroup(LayoutGroup group)
+    {
+        if (group.Children == null)
+        {
+            AlignNodes(group.Nodes, group.SourceNode);
+            return;
+        }
+
+        int childGroupsCount = group.Children.Count;
+        double totalHeightRequired = 0;
+
+        for (int i = 0; i < childGroupsCount; i++)
+        {
+            RectD bounds = GetBounds(group.Nodes);
+            totalHeightRequired += bounds.Height;
+        }
+        
+        double startY = 0;
+
+        for (int i = 0; i < group.Children.Count; i++)
+        {
+            var childGroup = group.Children[i];
+
+            RectD alignTo = new RectD(VecD.Zero, new VecD(0, 0));
+            if (childGroup.SourceNode != null)
+            {
+                alignTo = new RectD(boundsMap[childGroup.SourceNode].Pos, NodeSizes[childGroup.SourceNode]);
+            }
+
+            RectD bounds = GetBounds(childGroup.Nodes);
+
+            double shift = -totalHeightRequired / 2f + bounds.Height / 2f;
+            double moveYby = shift + startY + VerticalSpacing * i;
+
+            double x = alignTo.X - alignTo.Width - HorizontalSpacing;
+            double y = alignTo.Y + moveYby;
+
+            foreach (var node in childGroup.Nodes)
+            {
+                node.PositionBindable = new VecD(boundsMap[node].X, boundsMap[node].Y);
+            }
+            
+            startY += bounds.Height;
+        }
+    }
+
+    private LayoutGroup SplitToGroups(INodeHandler startNode, INodeHandler sourceNode, LayoutGroup parent = null)
+    {
+        var group = new LayoutGroup { Nodes = [startNode], Parent = parent, SourceNode = sourceNode };
+
+        startNode.TraverseBackwards((node, connection) =>
+        {
+            if (group.Nodes.Contains(node))
+            {
+                return true;
+            }
+
+            group.Nodes.Add(node);
+
+            if (HasMultipleDistinctConnections(node, out var distinctNodes))
+            {
+                group.Children ??= new List<LayoutGroup>();
+                foreach (var distinctNode in distinctNodes)
+                {
+                    group.Children.Add(SplitToGroups(distinctNode, node, group));
+                }
+            }
+
+            return true;
+        });
+
+        return group;
+    }
+
+    private RectD GetBounds(List<INodeHandler> nodes)
+    {
+        double minX = double.MaxValue;
+        double minY = double.MaxValue;
+        double maxX = double.MinValue;
+        double maxY = double.MinValue;
+
+        foreach (var node in nodes)
+        {
+            var position = boundsMap[node].TopLeft;
+            minX = Math.Min(minX, position.X);
+            minY = Math.Min(minY, position.Y);
+            maxX = Math.Max(maxX, position.X + NodeSizes[node].X);
+            maxY = Math.Max(maxY, position.Y + NodeSizes[node].Y);
+        }
+
+        return new RectD(minX, minY, maxX - minX, maxY - minY);
+    }
+
+    private bool HasMultipleDistinctConnections(INodeHandler node, out List<INodeHandler> distinctNodes)
+    {
+        List<INodeHandler> connectedNodes = new();
+        foreach (var input in node.Inputs)
+        {
+            if (input.ConnectedOutput != null && !connectedNodes.Contains(input.ConnectedOutput.Node))
+            {
+                connectedNodes.Add(input.ConnectedOutput.Node);
+            }
+        }
+
+        distinctNodes = connectedNodes;
+        return connectedNodes.Count > 1;
+    }
+
+    public static Queue<LayoutGroup> CalculateExecutionQueue(LayoutGroup outputNode)
+    {
+        var finalQueue = new HashSet<LayoutGroup>();
+        var queueNodes = new Queue<LayoutGroup>();
+        queueNodes.Enqueue(outputNode);
+
+        while (queueNodes.Count > 0)
+        {
+            var node = queueNodes.Dequeue();
+
+            if (finalQueue.Contains(node))
+            {
+                continue;
+            }
+
+            bool canAdd = true;
+
+            if (node.Children == null)
+            {
+                finalQueue.Add(node);
+                continue;
+            }
+
+            foreach (var input in node.Children)
+            {
+                if (finalQueue.Contains(input))
+                {
+                    continue;
+                }
+
+                canAdd = false;
+
+                if (finalQueue.Contains(input))
+                {
+                    finalQueue.Remove(input);
+                    finalQueue.Add(input);
+                }
+
+                if (!queueNodes.Contains(input))
+                {
+                    queueNodes.Enqueue(input);
+                }
+            }
+
+            if (canAdd)
+            {
+                finalQueue.Add(node);
+            }
+            else
+            {
+                queueNodes.Enqueue(node);
+            }
+        }
+
+        return new Queue<LayoutGroup>(finalQueue);
+    }
+}
+
+public class LayoutGroup
+{
+    public LayoutGroup Parent { get; set; }
+    public List<LayoutGroup> Children { get; set; }
+    public List<INodeHandler> Nodes { get; set; }
+    public INodeHandler SourceNode { get; set; }
+}

+ 99 - 31
src/PixiEditor/Views/Nodes/NodeGraphView.cs

@@ -1,4 +1,5 @@
-using System.Collections.ObjectModel;
+using System.Collections;
+using System.Collections.ObjectModel;
 using System.Collections.Specialized;
 using System.ComponentModel;
 using System.Windows.Input;
@@ -231,7 +232,7 @@ internal class NodeGraphView : Zoombox.Zoombox
         nodeItemsControl = e.NameScope.Find<ItemsControl>("PART_Nodes");
         connectionItemsControl = e.NameScope.Find<ItemsControl>("PART_Connections");
         selectionRectangle = e.NameScope.Find<Rectangle>("PART_SelectionRectangle");
-        
+
         Button sortButton = e.NameScope.Find<Button>("PART_SortButton");
         sortButton.Click += SortButton_Click;
 
@@ -239,31 +240,30 @@ internal class NodeGraphView : Zoombox.Zoombox
         {
             nodeItemsControl.ItemsPanelRoot.Children.CollectionChanged += NodeItems_CollectionChanged;
             nodeViewsCache = nodeItemsControl.ItemsPanelRoot.Children.ToList();
+            HandleNodesAdded(nodeViewsCache);
         });
     }
 
+    protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
+    {
+        nodeItemsControl.ItemsPanelRoot.Children.CollectionChanged -= NodeItems_CollectionChanged;
+    }
+
+    protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+    {
+        if (nodeItemsControl is { ItemsPanelRoot: not null })
+        {
+            nodeItemsControl.ItemsPanelRoot.Children.CollectionChanged += NodeItems_CollectionChanged;
+            nodeViewsCache = nodeItemsControl.ItemsPanelRoot.Children.ToList();
+            HandleNodesAdded(nodeViewsCache);
+        }
+    }
+
     private void NodeItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
     {
         if (e.Action == NotifyCollectionChangedAction.Add)
         {
-            foreach (Control control in e.NewItems)
-            {
-                if (control is not ContentPresenter presenter)
-                {
-                    continue;
-                }
-
-                nodeViewsCache.Add(presenter);
-                presenter.PropertyChanged += OnPresenterPropertyChanged;
-                if (presenter.Child == null)
-                {
-                    continue;
-                }
-
-                NodeView nodeView = (NodeView)presenter.Child;
-                nodeView.PropertyChanged += NodeView_PropertyChanged;
-                nodeView.Node.PropertyChanged += Node_PropertyChanged;
-            }
+            HandleNodesAdded(e.NewItems);
         }
         else if (e.Action == NotifyCollectionChangedAction.Remove)
         {
@@ -294,6 +294,32 @@ internal class NodeGraphView : Zoombox.Zoombox
         }
     }
 
+    private void HandleNodesAdded(IList? items)
+    {
+        foreach (Control control in items)
+        {
+            if (control is not ContentPresenter presenter)
+            {
+                continue;
+            }
+
+            if (!nodeViewsCache.Contains(presenter))
+            {
+                nodeViewsCache.Add(presenter);
+            }
+
+            presenter.PropertyChanged += OnPresenterPropertyChanged;
+            if (presenter.Child == null)
+            {
+                continue;
+            }
+
+            NodeView nodeView = (NodeView)presenter.Child;
+            nodeView.PropertyChanged += NodeView_PropertyChanged;
+            nodeView.Node.PropertyChanged += Node_PropertyChanged;
+        }
+    }
+
     private void OnPresenterPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
     {
         if (e.Property == ContentPresenter.ChildProperty)
@@ -605,6 +631,8 @@ internal class NodeGraphView : Zoombox.Zoombox
         {
             if (connection is ContentPresenter contentPresenter)
             {
+                if (!contentPresenter.IsLoaded) continue;
+
                 ConnectionView connectionView = (ConnectionView)contentPresenter.FindDescendantOfType<ConnectionView>();
 
                 if (connectionView.InputProperty == propertyView || connectionView.OutputProperty == propertyView)
@@ -644,7 +672,8 @@ internal class NodeGraphView : Zoombox.Zoombox
             return;
         }
 
-        (INodePropertyHandler, INodePropertyHandler, INodePropertyHandler?) connection = (startConnectionProperty, null, null);
+        (INodePropertyHandler, INodePropertyHandler, INodePropertyHandler?) connection = (startConnectionProperty, null,
+            null);
         if (socket != null)
         {
             endConnectionNode = socket.Node;
@@ -718,10 +747,11 @@ internal class NodeGraphView : Zoombox.Zoombox
             node.IsNodeSelected = false;
         }
     }
-    
+
     private void SortButton_Click(object sender, RoutedEventArgs e)
     {
         Dictionary<INodeHandler, RectD> boundsMap = new();
+
         foreach (var nodeView in nodeViewsCache)
         {
             if (nodeView is ContentPresenter { Child: NodeView node })
@@ -730,21 +760,59 @@ internal class NodeGraphView : Zoombox.Zoombox
                 boundsMap[node.Node] = new RectD(bounds.X, bounds.Y, bounds.Width, bounds.Height);
             }
         }
-        
-        const double padding = 50;
 
-        NodeGraph.OutputNode.TraverseBackwards((node, previousNode) =>
+        LayoutAligner aligner = new(NodeGraph, boundsMap);
+        aligner.SplitToGroups();
+        aligner.Align();
+
+        /*const double padding = 100;
+
+        NodeGraph.OutputNode.TraverseBackwards((node, previousNode, previousConnection) =>
         {
-            if(previousNode == null) return true;
-            
+            if (previousNode == null)
+            {
+                boundsMap[node] = new RectD(VecD.Zero, boundsMap[node].Size);
+                node.PositionBindable = new VecD(0, 0);
+                return true;
+            }
+
             RectD previousBounds = boundsMap[previousNode];
             RectD bounds = boundsMap[node];
-            
-            bounds = new RectD(previousBounds.X - previousBounds.Width - padding, previousBounds.Y, bounds.Width, bounds.Height);
-            
+
+            int verticalCount = previousNode.Inputs.Count(x => x.ConnectedOutput != null);
+            double totalHeightRequired = 0;
+            double startY = 0;
+
+            int indexOfVertical = 0;
+            bool found = false;
+            foreach (var input in previousNode.Inputs)
+            {
+                if (input.ConnectedOutput == null) continue;
+
+                if (input != previousConnection && !found)
+                {
+                    indexOfVertical++;
+                }
+                else if (!found)
+                {
+                    startY = totalHeightRequired;
+                    found = true;
+                }
+
+                totalHeightRequired += boundsMap[input.ConnectedOutput.Node].Height;
+            }
+
+            double shift = -totalHeightRequired / 2f + bounds.Height / 2f;
+            double moveYby = shift + startY + padding * indexOfVertical;
+
+            double x = previousBounds.X - previousBounds.Width - padding;
+            double y = verticalCount == 1 ? 0 : previousBounds.Y + moveYby;
+
+            bounds = new RectD(x, y, bounds.Width, bounds.Height);
+
             node.PositionBindable = bounds.TopLeft;
             boundsMap[node] = bounds;
             return true;
-        });
+        });*/
     }
 }