diff --git a/Source/Editor/Surface/VisjectSurface.ContextMenu.cs b/Source/Editor/Surface/VisjectSurface.ContextMenu.cs index ae836bb11..98508046d 100644 --- a/Source/Editor/Surface/VisjectSurface.ContextMenu.cs +++ b/Source/Editor/Surface/VisjectSurface.ContextMenu.cs @@ -199,6 +199,8 @@ namespace FlaxEditor.Surface private ContextMenuButton _cmAlignNodesLeftButton; private ContextMenuButton _cmAlignNodesCenterButton; private ContextMenuButton _cmAlignNodesRightButton; + private ContextMenuButton _cmDistributeNodesHorizontallyButton; + private ContextMenuButton _cmDistributeNodesVerticallyButton; private ContextMenuButton _cmRemoveNodeConnectionsButton; private ContextMenuButton _cmRemoveBoxConnectionsButton; private readonly Float2 ContextMenuOffset = new Float2(5); @@ -421,6 +423,10 @@ namespace FlaxEditor.Surface _cmAlignNodesCenterButton = _cmFormatNodesMenu.ContextMenu.AddButton("Align center", () => { AlignNodes(SelectedNodes, NodeAlignmentType.Center); }); _cmAlignNodesRightButton = _cmFormatNodesMenu.ContextMenu.AddButton("Align right", () => { AlignNodes(SelectedNodes, NodeAlignmentType.Right); }); + _cmFormatNodesMenu.ContextMenu.AddSeparator(); + _cmDistributeNodesHorizontallyButton = _cmFormatNodesMenu.ContextMenu.AddButton("Distribute horizontally", () => { DistributeNodes(SelectedNodes, false); }); + _cmDistributeNodesVerticallyButton = _cmFormatNodesMenu.ContextMenu.AddButton("Distribute vertically", () => { DistributeNodes(SelectedNodes, true); }); + _cmRemoveNodeConnectionsButton = menu.AddButton("Remove all connections to that node(s)", () => { var nodes = ((List)menu.Tag); diff --git a/Source/Editor/Surface/VisjectSurface.Formatting.cs b/Source/Editor/Surface/VisjectSurface.Formatting.cs index 6b942f4a1..2ff48b290 100644 --- a/Source/Editor/Surface/VisjectSurface.Formatting.cs +++ b/Source/Editor/Surface/VisjectSurface.Formatting.cs @@ -324,5 +324,80 @@ namespace FlaxEditor.Surface MarkAsEdited(false); Undo?.AddAction(new MultiUndoAction(undoActions, $"Align nodes ({alignmentType})")); } + + /// + /// Distribute the given nodes as equally as possible inside the bounding box, if no fit can be done it will use a default pad of 10 pixels between nodes. + /// + /// List of nodes + /// If false will be done horizontally, if true will be done vertically + public void DistributeNodes(List nodes, bool vertically) + { + if(nodes.Count <= 1) + return; + + var undoActions = new List(); + var boundingBox = GetNodesBounds(nodes); + float padding = 10; + float totalSize = 0; + for (int i = 0; i < nodes.Count; i++) + { + if (vertically) + { + totalSize += nodes[i].Height; + } + else + { + totalSize += nodes[i].Width; + } + } + + if(vertically) + { + nodes.Sort((leftValue, rightValue) => { return leftValue.Y.CompareTo(rightValue.Y); }); + + float position = boundingBox.Top; + if(totalSize < boundingBox.Height) + { + padding = (boundingBox.Height - totalSize) / nodes.Count; + } + + for(int i = 0; i < nodes.Count; i++) + { + var newLocation = new Float2(nodes[i].X, position); + var locationDelta = newLocation - nodes[i].Location; + nodes[i].Location = newLocation; + + position += nodes[i].Height + padding; + + if (Undo != null) + undoActions.Add(new MoveNodesAction(Context, new[] { nodes[i].ID }, locationDelta)); + } + } + else + { + nodes.Sort((leftValue, rightValue) => { return leftValue.X.CompareTo(rightValue.X); }); + + float position = boundingBox.Left; + if(totalSize < boundingBox.Width) + { + padding = (boundingBox.Width - totalSize) / nodes.Count; + } + + for(int i = 0; i < nodes.Count; i++) + { + var newLocation = new Float2(position, nodes[i].Y); + var locationDelta = newLocation - nodes[i].Location; + nodes[i].Location = newLocation; + + position += nodes[i].Width + padding; + + if (Undo != null) + undoActions.Add(new MoveNodesAction(Context, new[] { nodes[i].ID }, locationDelta)); + } + } + + MarkAsEdited(false); + Undo?.AddAction(new MultiUndoAction(undoActions, vertically ? "Distribute nodes vertically" : "Distribute nodes horizontally")); + } } }