Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Node auto layout for node autocomplete #11177

Merged
merged 18 commits into from
Oct 15, 2020
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions src/DynamoCore/Graph/Workspaces/LayoutExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public static class LayoutExtensions
/// This function wraps a few methods on the workspace model layer
/// to set up and run the graph layout algorithm.
/// </summary>
internal static List<GraphLayout.Graph> DoGraphAutoLayout(this WorkspaceModel workspace)
internal static List<GraphLayout.Graph> DoGraphAutoLayout(this WorkspaceModel workspace, bool reuseUndoRecorder = false)
aparajit-pratap marked this conversation as resolved.
Show resolved Hide resolved
{
if (workspace.Nodes.Count() < 2) return null;

Expand All @@ -32,9 +32,11 @@ public static class LayoutExtensions
List<GraphLayout.Graph> layoutSubgraphs;
List<List<GraphLayout.Node>> subgraphClusters;

GenerateCombinedGraph(workspace, isGroupLayout,out layoutSubgraphs, out subgraphClusters);
GenerateCombinedGraph(workspace, isGroupLayout, out layoutSubgraphs, out subgraphClusters);

RecordUndoGraphLayout(workspace, isGroupLayout);

RecordUndoGraphLayout(workspace, isGroupLayout, reuseUndoRecorder);


// Generate subgraphs separately for each cluster
subgraphClusters.ForEach(
Expand Down Expand Up @@ -194,7 +196,8 @@ private static void GenerateCombinedGraph(this WorkspaceModel workspace, bool is
/// </summary>
/// <param name="workspace">A <see cref="WorkspaceModel"/>.</param>
/// <param name="isGroupLayout">True if all the selected models are groups.</param>
private static void RecordUndoGraphLayout(this WorkspaceModel workspace, bool isGroupLayout)
/// <param name="reuseUndoRecorder">Initialize new UndoRedoRecorder if false.</param>
private static void RecordUndoGraphLayout(this WorkspaceModel workspace, bool isGroupLayout, bool reuseUndoRecorder)
{
List<ModelBase> undoItems = new List<ModelBase>();

Expand All @@ -221,7 +224,14 @@ private static void RecordUndoGraphLayout(this WorkspaceModel workspace, bool is
}
}

WorkspaceModel.RecordModelsForModification(undoItems, workspace.UndoRecorder);
if (reuseUndoRecorder)
{
workspace.RecordModelsForModification(undoItems);
}
else
{
WorkspaceModel.RecordModelsForModification(undoItems, workspace.UndoRecorder);
}
}

/// <summary>
Expand Down
10 changes: 10 additions & 0 deletions src/DynamoCore/Graph/Workspaces/UndoRedo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,16 @@ internal static void RecordModelsForModification(List<ModelBase> models, UndoRed
}
}

internal void RecordModelsForModification(List<ModelBase> models)
{
if (null == UndoRecorder) return;

if (!ShouldProceedWithRecording(models)) return;

foreach (var model in models)
UndoRecorder.RecordModificationForUndo(model);
}

internal static void RecordModelsForUndo(Dictionary<ModelBase, UndoRedoRecorder.UserAction> models, UndoRedoRecorder recorder)
{
if (null == recorder)
Expand Down
75 changes: 41 additions & 34 deletions src/DynamoCore/Models/DynamoModelCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,43 +83,50 @@ private void CreateAndConnectNodeImpl(CreateAndConnectNodeCommand command)
{
using (CurrentWorkspace.UndoRecorder.BeginActionGroup())
{
var newNode = CreateNodeFromNameOrType(command.ModelGuid, command.NewNodeName);
newNode.X = command.X;
newNode.Y = command.Y;
var existingNode = CurrentWorkspace.GetModelInternal(command.ModelGuids.ElementAt(1)) as NodeModel;

if(newNode == null || existingNode == null) return;

AddNodeToCurrentWorkspace(newNode, false, command.AddNewNodeToSelection);
CurrentWorkspace.UndoRecorder.RecordCreationForUndo(newNode);

PortModel inPortModel, outPortModel;
if (command.CreateAsDownstreamNode)
{
// Connect output port of Existing Node to input port of New node
outPortModel = existingNode.OutPorts[command.OutputPortIndex];
inPortModel = newNode.InPorts[command.InputPortIndex];
}
else
{
// Connect output port of New Node to input port of existing node
outPortModel = newNode.OutPorts[command.OutputPortIndex];
inPortModel = existingNode.InPorts[command.InputPortIndex];
}
CreateAndConnectNodeImplWithUndoRecorder(command);
}
}

private void CreateAndConnectNodeImplWithUndoRecorder(CreateAndConnectNodeCommand command)
{
var newNode = CreateNodeFromNameOrType(command.ModelGuid, command.NewNodeName);

newNode.X = command.X;
aparajit-pratap marked this conversation as resolved.
Show resolved Hide resolved
newNode.Y = command.Y;

var models = GetConnectorsToAddAndDelete(inPortModel, outPortModel);
var existingNode = CurrentWorkspace.GetModelInternal(command.ModelGuids.ElementAt(1)) as NodeModel;

foreach (var modelPair in models)
if (existingNode == null) return;

AddNodeToCurrentWorkspace(newNode, false, command.AddNewNodeToSelection);
CurrentWorkspace.UndoRecorder.RecordCreationForUndo(newNode);

PortModel inPortModel, outPortModel;
if (command.CreateAsDownstreamNode)
{
// Connect output port of Existing Node to input port of New node
outPortModel = existingNode.OutPorts[command.OutputPortIndex];
inPortModel = newNode.InPorts[command.InputPortIndex];
}
else
{
// Connect output port of New Node to input port of existing node
outPortModel = newNode.OutPorts[command.OutputPortIndex];
inPortModel = existingNode.InPorts[command.InputPortIndex];
}

var models = GetConnectorsToAddAndDelete(inPortModel, outPortModel);

foreach (var modelPair in models)
{
switch (modelPair.Value)
{
switch (modelPair.Value)
{
case UndoRedoRecorder.UserAction.Creation:
CurrentWorkspace.UndoRecorder.RecordCreationForUndo(modelPair.Key);
break;
case UndoRedoRecorder.UserAction.Deletion:
CurrentWorkspace.UndoRecorder.RecordDeletionForUndo(modelPair.Key);
break;
}
case UndoRedoRecorder.UserAction.Creation:
CurrentWorkspace.UndoRecorder.RecordCreationForUndo(modelPair.Key);
break;
case UndoRedoRecorder.UserAction.Deletion:
QilongTang marked this conversation as resolved.
Show resolved Hide resolved
CurrentWorkspace.UndoRecorder.RecordDeletionForUndo(modelPair.Key);
break;
}
}
}
Expand Down
38 changes: 35 additions & 3 deletions src/DynamoCore/Models/RecordableCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -641,8 +641,6 @@ protected override void SerializeCore(XmlElement element)
[DataContract]
public class CreateNodeCommand : ModelBasedRecordableCommand
{
#region Public Class Methods

private void SetProperties(double x, double y, bool defaultPosition, bool transformCoordinates)
{
X = x;
Expand All @@ -651,6 +649,8 @@ private void SetProperties(double x, double y, bool defaultPosition, bool transf
TransformCoordinates = transformCoordinates;
}

#region Public Class Methods

/// <summary>
/// </summary>
/// <param name="node">The node.</param>
Expand Down Expand Up @@ -817,6 +817,31 @@ internal override void TrackAnalytics()
[DataContract]
public class CreateAndConnectNodeCommand : ModelBasedRecordableCommand
{
private bool reuseUndoRecorder;

/// <summary>
/// Creates a new CreateAndConnectNodeCommand with the given inputs
/// </summary>
/// <param name="newNodeGuid"></param>
/// <param name="existingNodeGuid"></param>
/// <param name="newNodeName">The name of node to create</param>
/// <param name="outPortIndex"></param>
/// <param name="inPortIndex"></param>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="createAsDownstreamNode">
/// new node to be created as downstream or upstream node wrt the existing node
/// </param>
/// <param name="addNewNodeToSelection">select the new node after it is created by default</param>
/// <param name="reuseUndoRecorder"></param>
internal CreateAndConnectNodeCommand(Guid newNodeGuid, Guid existingNodeGuid, string newNodeName,
int outPortIndex, int inPortIndex,
double x, double y, bool createAsDownstreamNode, bool addNewNodeToSelection, bool reuseUndoRecorder)
: this(newNodeGuid, existingNodeGuid, newNodeName, outPortIndex, inPortIndex,
x, y, createAsDownstreamNode, addNewNodeToSelection)
{
this.reuseUndoRecorder = reuseUndoRecorder;
}

#region Public Class Methods

Expand Down Expand Up @@ -879,7 +904,14 @@ public CreateAndConnectNodeCommand(Guid newNodeGuid, Guid existingNodeGuid, stri

protected override void ExecuteCore(DynamoModel dynamoModel)
{
dynamoModel.CreateAndConnectNodeImpl(this);
if (reuseUndoRecorder)
{
dynamoModel.CreateAndConnectNodeImplWithUndoRecorder(this);
}
else
{
dynamoModel.CreateAndConnectNodeImpl(this);
}
}

protected override void SerializeCore(XmlElement element)
Expand Down
64 changes: 59 additions & 5 deletions src/DynamoCoreWpf/ViewModels/Search/SearchViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@
using System.Windows;
using System.Windows.Media;
using Dynamo.Configuration;
using Dynamo.Controls;
using Dynamo.Graph.Nodes;
using Dynamo.Graph.Workspaces;
using Dynamo.Interfaces;
using Dynamo.Logging;
using Dynamo.Models;
using Dynamo.Search;
using Dynamo.Search.SearchElements;
using Dynamo.Selection;
using Dynamo.UI;
using Dynamo.Utilities;
using Dynamo.Wpf.Services;
Expand Down Expand Up @@ -44,6 +47,7 @@ public void OnSearchTextChanged(object sender, EventArgs e)
#region Properties/Fields

private readonly IconServices iconServices;
private IDisposable undoRecorderGroup;

/// <summary>
/// Position, where canvas was clicked.
Expand Down Expand Up @@ -345,6 +349,14 @@ public override void Dispose()
}
Model.EntryUpdated -= UpdateEntry;
Model.EntryRemoved -= RemoveEntry;

if (undoRecorderGroup != null)
{
undoRecorderGroup.Dispose();
undoRecorderGroup = null;

dynamoViewModel.NodeViewReady -= AutoLayoutNodes;
}
base.Dispose();
}

Expand Down Expand Up @@ -1061,14 +1073,56 @@ public void OnSearchElementClicked(NodeModel nodeModel, Point position)

internal void OnRequestConnectToPort(string nodeCreationName, PortModel portModel)
{
if (!nodeCreationName.Contains("Code Block"))
// Do not auto connect code block node since default code block node do not have output port
if (nodeCreationName.Contains("Code Block")) return;

var initialNode = dynamoViewModel.CurrentSpaceViewModel.Nodes.FirstOrDefault(x => x.Id == portModel.Owner.GUID);
var id = Guid.NewGuid();

var adjustedX = initialNode.X;

// Placing the new node based on which input port it is connecting to.
if (portModel.PortType == PortType.Input)
{
// Create a new node based on node creation name and connect ports
dynamoViewModel.ExecuteCommand(new DynamoModel.CreateAndConnectNodeCommand(Guid.NewGuid(), portModel.Owner.GUID,
nodeCreationName, 0, portModel.Index, portModel.CenterX - 50, portModel.CenterY + 200, false, false));
// Placing the new node to the left of initial node
adjustedX -= portModel.Owner.Width + 50;
}
else
{
adjustedX += portModel.Owner.Width + 50;
}

OnRequestFocusSearch();
if (undoRecorderGroup == null)
{
dynamoViewModel.NodeViewReady += AutoLayoutNodes;
undoRecorderGroup = dynamoViewModel.CurrentSpace.UndoRecorder.BeginActionGroup();
}

// Create a new node based on node creation name and connect ports
dynamoViewModel.ExecuteCommand(new DynamoModel.CreateAndConnectNodeCommand(id, portModel.Owner.GUID,
nodeCreationName, 0, portModel.Index, adjustedX, 0, false, false, true));

// Clear current selections and select all input nodes
DynamoSelection.Instance.ClearSelection();
var inputNodes = portModel.Owner.InputNodes.Values.Where(x => x != null).Select(y => y.Item2);
Copy link
Contributor

@QilongTang QilongTang Oct 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain the Item2 part? I think I tried this one before by just calling Select and did not work.. Maybe i missed something. You are doing some smart thing for sure 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha, nothing smart here. InputNodes property on NodeModel is of type IDictionary<int, Tuple<int, NodeModel>>. I'm just getting the NodeModel part of the tuple.


foreach (var inputNode in inputNodes)
{
DynamoSelection.Instance.Selection.AddUnique(inputNode);
aparajit-pratap marked this conversation as resolved.
Show resolved Hide resolved
}
}

private void AutoLayoutNodes(object sender, EventArgs e)
{
dynamoViewModel.CurrentSpace.DoGraphAutoLayout(true);

if (undoRecorderGroup != null)
{
undoRecorderGroup.Dispose();
undoRecorderGroup = null;

dynamoViewModel.NodeViewReady -= AutoLayoutNodes;
}
}

#endregion
Expand Down