Merge branch 'xxSeys1-VjStraightenConnections'

This commit is contained in:
Wojtek Figat
2025-08-19 13:20:44 +02:00
5 changed files with 113 additions and 47 deletions

View File

@@ -652,43 +652,47 @@ namespace FlaxEditor.Options
#endregion
#region Node editors
#region Node Editors
[DefaultValue(typeof(InputBinding), "Shift+W")]
[EditorDisplay("Node editors"), EditorOrder(4500)]
[EditorDisplay("Node Editors"), EditorOrder(4500)]
public InputBinding NodesAlignTop = new InputBinding(KeyboardKeys.W, KeyboardKeys.Shift);
[DefaultValue(typeof(InputBinding), "Shift+A")]
[EditorDisplay("Node editors"), EditorOrder(4510)]
[EditorDisplay("Node Editors"), EditorOrder(4510)]
public InputBinding NodesAlignLeft = new InputBinding(KeyboardKeys.A, KeyboardKeys.Shift);
[DefaultValue(typeof(InputBinding), "Shift+S")]
[EditorDisplay("Node editors"), EditorOrder(4520)]
[EditorDisplay("Node Editors"), EditorOrder(4520)]
public InputBinding NodesAlignBottom = new InputBinding(KeyboardKeys.S, KeyboardKeys.Shift);
[DefaultValue(typeof(InputBinding), "Shift+D")]
[EditorDisplay("Node editors"), EditorOrder(4530)]
[EditorDisplay("Node Editors"), EditorOrder(4530)]
public InputBinding NodesAlignRight = new InputBinding(KeyboardKeys.D, KeyboardKeys.Shift);
[DefaultValue(typeof(InputBinding), "Alt+Shift+W")]
[EditorDisplay("Node editors"), EditorOrder(4540)]
[EditorDisplay("Node Editors"), EditorOrder(4540)]
public InputBinding NodesAlignMiddle = new InputBinding(KeyboardKeys.W, KeyboardKeys.Shift, KeyboardKeys.Alt);
[DefaultValue(typeof(InputBinding), "Alt+Shift+S")]
[EditorDisplay("Node editors"), EditorOrder(4550)]
[EditorDisplay("Node Editors"), EditorOrder(4550)]
public InputBinding NodesAlignCenter = new InputBinding(KeyboardKeys.S, KeyboardKeys.Shift, KeyboardKeys.Alt);
[DefaultValue(typeof(InputBinding), "Q")]
[EditorDisplay("Node editors"), EditorOrder(4560)]
[EditorDisplay("Node Editors"), EditorOrder(4560)]
public InputBinding NodesAutoFormat = new InputBinding(KeyboardKeys.Q);
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("Node editors"), EditorOrder(4570)]
public InputBinding NodesDistributeHorizontal = new InputBinding(KeyboardKeys.None);
[DefaultValue(typeof(InputBinding), "Shift+Q")]
[EditorDisplay("Node Editors"), EditorOrder(4560)]
public InputBinding NodesStraightenConnections = new InputBinding(KeyboardKeys.Q, KeyboardKeys.Shift);
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("Node editors"), EditorOrder(4580)]
public InputBinding NodesDistributeVertical = new InputBinding(KeyboardKeys.None);
[DefaultValue(typeof(InputBinding), "Alt+W")]
[EditorDisplay("Node Editors"), EditorOrder(4570)]
public InputBinding NodesDistributeHorizontal = new InputBinding(KeyboardKeys.W, KeyboardKeys.Alt);
[DefaultValue(typeof(InputBinding), "Alt+A")]
[EditorDisplay("Node Editors"), EditorOrder(4580)]
public InputBinding NodesDistributeVertical = new InputBinding(KeyboardKeys.A, KeyboardKeys.Alt);
#endregion
}

View File

@@ -5,39 +5,39 @@ using FlaxEngine;
namespace FlaxEditor.Surface
{
/// <summary>
/// Node Alignment type
/// Node Alignment type.
/// </summary>
[HideInEditor]
public enum NodeAlignmentType
{
/// <summary>
/// Align nodes vertically to top, matching top-most node
/// Align nodes vertically to top, matching top-most node.
/// </summary>
Top,
/// <summary>
/// Align nodes vertically to middle, using average of all nodes
/// Align nodes vertically to middle, using average of all nodes.
/// </summary>
Middle,
/// <summary>
/// Align nodes vertically to bottom, matching bottom-most node
/// Align nodes vertically to bottom, matching bottom-most node.
/// </summary>
Bottom,
/// <summary>
/// Align nodes horizontally to left, matching left-most node
/// Align nodes horizontally to left, matching left-most node.
/// </summary>
Left,
/// <summary>
/// Align nodes horizontally to center, using average of all nodes
/// Align nodes horizontally to center, using average of all nodes.
/// </summary>
Center,
/// <summary>
/// Align nodes horizontally to right, matching right-most node
/// Align nodes horizontally to right, matching right-most node.
/// </summary>
Right,
}
}
}

View File

@@ -28,7 +28,7 @@ namespace FlaxEditor.Surface
/// </summary>
/// <param name="scriptType">The input type to process.</param>
/// <param name="cache">Node groups cache that can be used for reusing groups for different nodes.</param>
/// <param name="version">The cache version number. Can be used to reject any cached data after <see cref="NodesCache"/> rebuilt.</param>
/// <param name="version">The cache version number. Can be used to reject any cached data after.<see cref="NodesCache"/> rebuilt.</param>
public delegate void IterateType(ScriptType scriptType, Dictionary<KeyValuePair<string, ushort>, GroupArchetype> cache, int version);
internal static readonly List<NodesCache> Caches = new List<NodesCache>(8);
@@ -412,6 +412,7 @@ namespace FlaxEditor.Surface
_cmFormatNodesMenu.Enabled = CanEdit && HasNodesSelection;
_cmFormatNodesConnectionButton = _cmFormatNodesMenu.ContextMenu.AddButton("Auto format", Editor.Instance.Options.Options.Input.NodesAutoFormat, () => { FormatGraph(SelectedNodes); });
_cmFormatNodesConnectionButton = _cmFormatNodesMenu.ContextMenu.AddButton("Straighten connections", Editor.Instance.Options.Options.Input.NodesStraightenConnections, () => { StraightenGraphConnections(SelectedNodes); });
_cmFormatNodesMenu.ContextMenu.AddSeparator();
_cmAlignNodesTopButton = _cmFormatNodesMenu.ContextMenu.AddButton("Align top", Editor.Instance.Options.Options.Input.NodesAlignTop, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Top); });

View File

@@ -1,9 +1,9 @@
using FlaxEditor.Surface.Elements;
using FlaxEditor.Surface.Undo;
using FlaxEngine;
using System;
using System.Collections.Generic;
using System.Linq;
using FlaxEngine;
using FlaxEditor.Surface.Elements;
using FlaxEditor.Surface.Undo;
namespace FlaxEditor.Surface
{
@@ -14,26 +14,26 @@ namespace FlaxEditor.Surface
private class NodeFormattingData
{
/// <summary>
/// Starting from 0 at the main nodes
/// Starting from 0 at the main nodes.
/// </summary>
public int Layer;
/// <summary>
/// Position in the layer
/// Position in the layer.
/// </summary>
public int Offset;
/// <summary>
/// How far the subtree needs to be moved additionally
/// How far the subtree needs to be moved additionally.
/// </summary>
public int SubtreeOffset;
}
/// <summary>
/// Formats a graph where the nodes can be disjointed.
/// Uses the Sugiyama method
/// Uses the Sugiyama method.
/// </summary>
/// <param name="nodes">List of nodes</param>
/// <param name="nodes">List of nodes.</param>
public void FormatGraph(List<SurfaceNode> nodes)
{
if (nodes.Count <= 1)
@@ -78,9 +78,9 @@ namespace FlaxEditor.Surface
}
/// <summary>
/// Formats a graph where all nodes are connected
/// Formats a graph where all nodes are connected.
/// </summary>
/// <param name="nodes">List of connected nodes</param>
/// <param name="nodes">List of connected nodes.</param>
protected void FormatConnectedGraph(List<SurfaceNode> nodes)
{
if (nodes.Count <= 1)
@@ -160,11 +160,71 @@ namespace FlaxEditor.Surface
}
/// <summary>
/// Assigns a layer to every node
/// Straightens every connection between nodes in <paramref name="nodes"/>.
/// </summary>
/// <param name="nodeData">The exta node data</param>
/// <param name="endNodes">The end nodes</param>
/// <returns>The number of the maximum layer</returns>
/// <param name="nodes">List of nodes.</param>
public void StraightenGraphConnections(List<SurfaceNode> nodes)
{
if (nodes.Count <= 1)
return;
List<MoveNodesAction> undoActions = new List<MoveNodesAction>();
// Only process nodes that have any connection
List<SurfaceNode> connectedNodes = nodes.Where(n => n.GetBoxes().Any(b => b.HasAnyConnection)).ToList();
if (connectedNodes.Count == 0)
return;
for (int i = 0; i < connectedNodes.Count - 1; i++)
{
SurfaceNode nodeA = connectedNodes[i];
List<Box> connectedOutputBoxes = nodeA.GetBoxes().Where(b => b.IsOutput && b.HasAnyConnection).ToList();
for (int j = 0; j < connectedOutputBoxes.Count; j++)
{
Box boxA = connectedOutputBoxes[j];
for (int b = 0; b < boxA.Connections.Count; b++)
{
Box boxB = boxA.Connections[b];
// Ensure the other node is selected
if (!connectedNodes.Contains(boxB.ParentNode))
continue;
// Node with no outgoing connections reached. Advance to next node in list
if (boxA == null || boxB == null)
continue;
SurfaceNode nodeB = boxB.ParentNode;
// Calculate the Y offset needed for nodeB to align boxB's Y to boxA's Y
float boxASurfaceY = boxA.PointToParent(this, Float2.Zero).Y;
float boxBSurfaceY = boxB.PointToParent(this, Float2.Zero).Y;
float deltaY = (boxASurfaceY - boxBSurfaceY) / ViewScale;
Float2 delta = new Float2(0f, deltaY);
nodeB.Location += delta;
if (Undo != null)
undoActions.Add(new MoveNodesAction(Context, new[] { nodeB.ID }, delta));
}
}
}
if (undoActions.Count > 0)
Undo?.AddAction(new MultiUndoAction(undoActions, "Straightned "));
MarkAsEdited(false);
}
/// <summary>
/// Assigns a layer to every node.
/// </summary>
/// <param name="nodeData">The exta node data.</param>
/// <param name="endNodes">The end nodes.</param>
/// <returns>The number of the maximum layer.</returns>
private int SetLayers(Dictionary<SurfaceNode, NodeFormattingData> nodeData, List<SurfaceNode> endNodes)
{
// Longest path layering
@@ -201,12 +261,12 @@ namespace FlaxEditor.Surface
/// <summary>
/// Sets the node offsets
/// Sets the node offsets.
/// </summary>
/// <param name="nodeData">The exta node data</param>
/// <param name="endNodes">The end nodes</param>
/// <param name="maxLayer">The number of the maximum layer</param>
/// <returns>The number of the maximum offset</returns>
/// <param name="nodeData">The exta node data.</param>
/// <param name="endNodes">The end nodes.</param>
/// <param name="maxLayer">The number of the maximum layer.</param>
/// <returns>The number of the maximum offset.</returns>
private int SetOffsets(Dictionary<SurfaceNode, NodeFormattingData> nodeData, List<SurfaceNode> endNodes, int maxLayer)
{
int maxOffset = 0;
@@ -287,10 +347,10 @@ namespace FlaxEditor.Surface
/// Align given nodes on a graph using the given alignment type.
/// Ignores any potential overlap.
/// </summary>
/// <param name="nodes">List of nodes</param>
/// <param name="alignmentType">Alignemnt type</param>
/// <param name="nodes">List of nodes.</param>
/// <param name="alignmentType">Alignemnt type.</param>
public void AlignNodes(List<SurfaceNode> nodes, NodeAlignmentType alignmentType)
{
{
if(nodes.Count <= 1)
return;
@@ -328,8 +388,8 @@ namespace FlaxEditor.Surface
/// <summary>
/// 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.
/// </summary>
/// <param name="nodes">List of nodes</param>
/// <param name="vertically">If false will be done horizontally, if true will be done vertically</param>
/// <param name="nodes">List of nodes.</param>
/// <param name="vertically">If false will be done horizontally, if true will be done vertically.</param>
public void DistributeNodes(List<SurfaceNode> nodes, bool vertically)
{
if(nodes.Count <= 1)

View File

@@ -416,6 +416,7 @@ namespace FlaxEditor.Surface
new InputActionsContainer.Binding(options => options.Cut, Cut),
new InputActionsContainer.Binding(options => options.Duplicate, Duplicate),
new InputActionsContainer.Binding(options => options.NodesAutoFormat, () => { FormatGraph(SelectedNodes); }),
new InputActionsContainer.Binding(options => options.NodesStraightenConnections, () => { StraightenGraphConnections(SelectedNodes); }),
new InputActionsContainer.Binding(options => options.NodesAlignTop, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Top); }),
new InputActionsContainer.Binding(options => options.NodesAlignMiddle, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Middle); }),
new InputActionsContainer.Binding(options => options.NodesAlignBottom, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Bottom); }),