Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 3 additions & 3 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
dotnet-version: 9.0.x

- name: Restore .NET dependencies
working-directory: ./src
Expand All @@ -48,7 +48,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
dotnet-version: 9.0.x

- uses: actions/download-artifact@v4
with:
Expand All @@ -68,7 +68,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
dotnet-version: 9.0.x

- uses: actions/download-artifact@master
with:
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@
/src/NodeDev.Core.Types.Tests/bin
/src/NodeDev.Core.Types.Tests/obj
/src/NodeDev.Core.Types/bin
/src/NodeDev.EndToEndTests/bin/Debug/net8.0
/src/NodeDev.EndToEndTests/bin
/src/NodeDev.EndToEndTests/obj
/src/NodeDev.Blazor.Server/project_backup.json
160 changes: 43 additions & 117 deletions src/NodeDev.Blazor/Components/GraphCanvas.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@
using System.Reactive.Linq;
using NodeDev.Core.Class;
using NodeDev.Blazor.Services;
using NodeDev.Blazor.Services.GraphManager;

namespace NodeDev.Blazor.Components;

public partial class GraphCanvas : ComponentBase, IDisposable
public partial class GraphCanvas : ComponentBase, IDisposable, IGraphCanvas
{
[Parameter, EditorRequired]
public Graph Graph { get; set; } = null!;
Expand All @@ -29,6 +30,9 @@ public partial class GraphCanvas : ComponentBase, IDisposable
[Inject]
internal DebuggedPathService DebuggedPathService { get; set; } = null!;

private GraphManagerService? _GraphManagerService;
private GraphManagerService GraphManagerService => _GraphManagerService ??= new GraphManagerService(this);

private int PopupX = 0;
private int PopupY = 0;
private Vector2 PopupNodePosition;
Expand Down Expand Up @@ -67,7 +71,6 @@ protected override void OnInitialized()
Diagram.Links.Added += x => OnConnectionAdded(x, false);
Diagram.Links.Removed += OnConnectionRemoved;
Diagram.SelectionChanged += SelectionChanged;

}

#endregion
Expand Down Expand Up @@ -138,7 +141,7 @@ private void OnGraphChangedFromCore((Graph, bool) _)

#region UpdateConnectionType

private void UpdateConnectionType(Connection connection)
public void UpdatePortColor(Connection connection)
{
var node = Diagram.Nodes.OfType<GraphNodeModel>().FirstOrDefault(x => x.Node == connection.Parent);
if (node == null)
Expand All @@ -155,16 +158,6 @@ private void UpdateConnectionType(Connection connection)

#endregion

#region UpdateNodeBaseInfo

private void UpdateNodeBaseInfo(Node node)
{
var nodeModel = Diagram.Nodes.OfType<GraphNodeModel>().First(x => x.Node == node);
nodeModel.UpdateNodeBaseInfo(node);
}

#endregion

#region UpdateNodes

private void UpdateNodes(IEnumerable<Node> nodes)
Expand Down Expand Up @@ -239,7 +232,7 @@ private void OnConnectionUpdated(BaseLinkModel baseLinkModel, Anchor old, Anchor
{
DisableConnectionUpdate = true;
var old = baseLinkModel.Source;
baseLinkModel.SetSource(baseLinkModel.Target);
baseLinkModel.SetSource(baseLinkModel.Target); // this is necessary as everything assumes that the source is an output and vice versa
baseLinkModel.SetTarget(old);
DisableConnectionUpdate = false;

Expand All @@ -248,47 +241,7 @@ private void OnConnectionUpdated(BaseLinkModel baseLinkModel, Anchor old, Anchor
destination = tmp;
}

Graph.Connect(source.Connection, destination.Connection, false);

// we're plugging something something with a generic into something without a generic
if (source.Connection.Type.HasUndefinedGenerics && !destination.Connection.Type.HasUndefinedGenerics)
{
if (source.Connection.Type.IsAssignableTo(destination.Connection.Type, out var newTypes) && newTypes.Count != 0)
{
PropagateNewGeneric(source.Connection.Parent, newTypes, false);
}
}
else if (destination.Connection.Type.HasUndefinedGenerics && !source.Connection.Type.HasUndefinedGenerics)
{
if (source.Connection.Type.IsAssignableTo(destination.Connection.Type, out var newTypes) && newTypes.Count != 0)
{
PropagateNewGeneric(destination.Connection.Parent, newTypes, false);
}
}

// we have to remove the textbox ?
if (destination.Connection.Connections.Count == 1 && destination.Connection.Type.AllowTextboxEdit)
UpdateConnectionType(destination.Connection);

if (baseLinkModel is LinkModel link && link.Source.Model is GraphPortModel sourcePort && link.Target.Model is GraphPortModel targetPort)
{
link.Color = GetTypeShapeColor(sourcePort.Connection.Type, sourcePort.Connection.Parent.TypeFactory);
// we have to disconnect the previously connected exec, since exec outputs can only have one connection
if (source.Connection.Type.IsExec && source.Connection.Connections.Count > 1)
{
Diagram.Links.Remove(Diagram.Links.First(x => (x.Source.Model as GraphPortModel)?.Connection == source.Connection && (x.Target.Model as GraphPortModel)?.Connection != targetPort.Connection));
Graph.Disconnect(source.Connection, source.Connection.Connections.First(x => x != targetPort.Connection), false);
}
else if (!destination.Connection.Type.IsExec && destination.Connection.Connections.Count > 1)
{
Diagram.Links.Remove(Diagram.Links.First(x => (x.Source.Model as GraphPortModel)?.Connection != source.Connection && (x.Target.Model as GraphPortModel)?.Connection == targetPort.Connection));
Graph.Disconnect(destination.Connection, destination.Connection.Connections.First(x => x != sourcePort.Connection), false);
}
}

UpdateVerticesInConnection(source.Connection, destination.Connection, baseLinkModel);

Graph.RaiseGraphChanged(true);
GraphManagerService.AddNewConnectionBetween(source.Connection, destination.Connection);
});
}

Expand Down Expand Up @@ -318,6 +271,11 @@ public void OnConnectionAdded(BaseLinkModel baseLinkModel, bool force)
}
}

/// <summary>
/// Return the output connection except for execs, in that case we return the input connection.
/// This is because vertices are stored for the port, and execs conveniently only have one output connection while other types only have one input connection.
/// </summary>
/// <returns></returns>
private Connection GetConnectionContainingVertices(Connection source, Connection destination)
{
if (source.Type.IsExec) // execs can only have one connection, therefor they always contains the vertex information
Expand Down Expand Up @@ -380,7 +338,7 @@ public void OnConnectionRemoved(BaseLinkModel baseLinkModel)

// We have to add back the textbox editor
if (destination.Connections.Count == 0 && destination.Type.AllowTextboxEdit)
UpdateConnectionType(destination);
UpdatePortColor(destination);

UpdateVerticesInConnection(source, destination, baseLinkModel);
}
Expand Down Expand Up @@ -433,11 +391,13 @@ private void OnNewNodeTypeSelected(NodeProvider.NodeSearchResult searchResult)

Diagram.Batch(() =>
{
CreateGraphNodeModel(node);

if (PopupNodeConnection != null && PopupNode != null)
{
// check if the source was an input or output and choose the proper destination based on that
List<Connection> sources, destinations;
bool isPopupNodeInput = PopupNode.Inputs.Contains(PopupNodeConnection);
bool isPopupNodeInput = PopupNodeConnection.IsInput;
if (isPopupNodeInput)
{
sources = PopupNode.Inputs;
Expand All @@ -453,43 +413,19 @@ private void OnNewNodeTypeSelected(NodeProvider.NodeSearchResult searchResult)
if (PopupNodeConnection.Type is UndefinedGenericType) // can connect to anything except exec
destination = destinations.FirstOrDefault(x => !x.Type.IsExec);
else // can connect to anything that is assignable to the type
destination = destinations.FirstOrDefault(x => PopupNodeConnection.Type.IsAssignableTo(x.Type, out _) || (x.Type is UndefinedGenericType && !PopupNodeConnection.Type.IsExec));
destination = destinations.FirstOrDefault(x => PopupNodeConnection.Type.IsAssignableTo(x.Type, out _, out _) || (x.Type is UndefinedGenericType && !PopupNodeConnection.Type.IsExec));

// if we found a connection, connect them together
if (destination != null)
{
Graph.Connect(PopupNodeConnection, destination, false);

if (destination.Connections.Count == 1 && destination.Type.AllowTextboxEdit)
UpdateConnectionType(destination);
if (PopupNodeConnection.Connections.Count == 1 && PopupNodeConnection.Type.AllowTextboxEdit)
UpdateConnectionType(PopupNodeConnection);

var source = isPopupNodeInput ? destination : PopupNodeConnection;
var target = isPopupNodeInput ? PopupNodeConnection : destination;

// check if we need to propagate some generic
if (!destination.Type.IsExec && source.Type.IsAssignableTo(target.Type, out var changedGenerics))
{
PropagateNewGeneric(node, changedGenerics, false);
PropagateNewGeneric(destination.Parent, changedGenerics, false);
}
else if (source.Type.IsExec && source.Connections.Count > 1) // check if we have to disconnect the previously connected exec
{
Diagram.Links.Remove(Diagram.Links.First(x => (x.Source.Model as GraphPortModel)?.Connection == source && (x.Target.Model as GraphPortModel)?.Connection != target));
var toRemove = source.Connections.FirstOrDefault(x => x != target);
if (toRemove != null)
Graph.Disconnect(source, toRemove, false);
}
GraphManagerService.AddNewConnectionBetween(source, target);
}
}

CancelPopup();

CreateGraphNodeModel(node);
AddNodeLinks(node, false);

UpdateNodes(Graph.Nodes.Values.ToList());
});

}
Expand All @@ -513,9 +449,7 @@ private void OnNewOverloadSelected(Node.AlternateOverload overload)
if (PopupNode == null)
return;

PopupNode.SelectOverload(overload, out var newConnections, out var removedConnections);

Graph.MergedRemovedConnectionsWithNewConnections(newConnections, removedConnections);
GraphManagerService.SelectNodeOverload(PopupNode, overload);

CancelPopup();
}
Expand All @@ -525,9 +459,9 @@ private void OnNewOverloadSelected(Node.AlternateOverload overload)
#region OnGenericTypeSelectionMenuAsked

private bool IsShowingGenericTypeSelection = false;
private UndefinedGenericType? GenericTypeSelectionMenuGeneric;
private string? GenericTypeSelectionMenuGeneric;

public void OnGenericTypeSelectionMenuAsked(GraphNodeModel nodeModel, UndefinedGenericType undefinedGenericType)
public void OnGenericTypeSelectionMenuAsked(GraphNodeModel nodeModel, string undefinedGenericType)
{
PopupNode = nodeModel.Node;
var p = Diagram.GetScreenPoint(nodeModel.Position.X, nodeModel.Position.Y) - Diagram.Container!.NorthWest;
Expand All @@ -544,42 +478,14 @@ private void OnGenericTypeSelected(TypeBase type)
if (PopupNode == null || GenericTypeSelectionMenuGeneric == null)
return;

PropagateNewGeneric(PopupNode, new Dictionary<UndefinedGenericType, TypeBase>() { [GenericTypeSelectionMenuGeneric] = type }, false);
GraphManagerService.PropagateNewGeneric(PopupNode, new Dictionary<string, TypeBase>() { [GenericTypeSelectionMenuGeneric] = type }, false, null, overrideInitialTypes: true);

// Prefer updating the nodes directly instead of calling Graph.RaiseGraphChanged(true) to be sure it is called as soon as possible
UpdateNodes(Graph.Nodes.Values.ToList());

CancelPopup();
}

private void PropagateNewGeneric(Node node, IReadOnlyDictionary<UndefinedGenericType, TypeBase> changedGenerics, bool requireUIRefresh)
{
foreach (var port in node.InputsAndOutputs) // check if any of the ports have the generic we just solved
{
if (port.Type.GetUndefinedGenericTypes().Any(changedGenerics.ContainsKey))
{
var isPortInput = node.Inputs.Contains(port);

port.UpdateType(port.Type.ReplaceUndefinedGeneric(changedGenerics));

UpdateConnectionType(port);

// check if other connections had their own generics and if we just solved them
foreach (var other in port.Connections.ToList())
{
var source = isPortInput ? other : port;
var target = isPortInput ? port : other;
if (source.Type.IsAssignableTo(target.Type, out var changedGenerics2) && changedGenerics2.Count != 0)
PropagateNewGeneric(other.Parent, changedGenerics2, requireUIRefresh);
else if ((changedGenerics2?.Count ?? 0) != 0)// damn, looks like changing the generic made it so we can't link to this connection anymore
Graph.Disconnect(port, other, false); // no need to refresh UI here as it'll already be refresh at the end of this method
}
}
}

Graph.RaiseGraphChanged(requireUIRefresh);
}

#endregion

#region OnTextboxValueChanged
Expand Down Expand Up @@ -664,6 +570,26 @@ private void CancelPopup()

#endregion

#region RemoveLink

public void RemoveLinkFromGraphCanvas(Connection source, Connection destination)
{
Graph.Invoke(() =>
{
DisableConnectionUpdate = true;
try
{
Diagram.Links.Remove(Diagram.Links.First(x => (x.Source.Model as GraphPortModel)?.Connection == source && (x.Target.Model as GraphPortModel)?.Connection == destination));
}
finally
{
DisableConnectionUpdate = false;
}
});
}

#endregion

#region CreateGraphNodeModel

private void CreateGraphNodeModel(Node node)
Expand Down
5 changes: 0 additions & 5 deletions src/NodeDev.Blazor/DiagramsModels/GraphNodeModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,6 @@ public class GraphNodeModel : NodeModel

public GraphPortModel GetPort(Connection connection) => Ports.OfType<GraphPortModel>().First( x=> x.Connection == connection);

internal void UpdateNodeBaseInfo(Node node)
{

}

internal void OnNodeExecuted(Connection exec)
{

Expand Down
2 changes: 1 addition & 1 deletion src/NodeDev.Blazor/DiagramsModels/GraphNodeWidget.razor
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<span class="ml-1">&lt;</span>
@foreach (var undefinedGeneric in undefinedGenerics)
{
<span class="ml-1 undefined-generic" @onclick="() => GraphCanvas.OnGenericTypeSelectionMenuAsked(Node, undefinedGeneric)">@(undefinedGeneric.Name + (undefinedGeneric == undefinedGenerics[^1] ? "" : ", "))</span>
<span class="ml-1 undefined-generic" @onclick="() => GraphCanvas.OnGenericTypeSelectionMenuAsked(Node, undefinedGeneric)">@(undefinedGeneric + (undefinedGeneric == undefinedGenerics[^1] ? "" : ", "))</span>
}
<span class="ml-1">&gt;</span>
}
Expand Down
2 changes: 1 addition & 1 deletion src/NodeDev.Blazor/DiagramsModels/GraphPortModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@ public override bool CanAttachTo(ILinkable other)
if(Alignment == otherPort.Alignment) // can't plug input to input or output to output
return false;

return Connection.Type.IsAssignableTo(otherPort.Connection.Type, out _);
return Connection.Type.IsAssignableTo(otherPort.Connection.Type, out _, out _);
}
}
Loading
Loading