diff --git a/Examples/Nodify.Calculator/CalculatorViewModel.cs b/Examples/Nodify.Calculator/CalculatorViewModel.cs
index 9f3b14d..c6b8dd6 100644
--- a/Examples/Nodify.Calculator/CalculatorViewModel.cs
+++ b/Examples/Nodify.Calculator/CalculatorViewModel.cs
@@ -28,6 +28,9 @@ namespace Nodify.Calculator
// Dynamic Split node: populate outputs when a model connects to the Square input
HandleSplitNodeConnected(c);
+
+ // Dynamic Copy node: adapt connectors when something connects
+ HandleCopyNodeConnected(c);
})
.WhenRemoved(c =>
{
@@ -48,6 +51,9 @@ namespace Nodify.Calculator
// Dynamic Split node: clear outputs when model disconnects
HandleSplitNodeDisconnected(c);
+
+ // Dynamic Copy node: reset on disconnect
+ HandleCopyNodeDisconnected(c);
});
Operations.WhenAdded(x =>
@@ -116,7 +122,8 @@ namespace Nodify.Calculator
internal bool CanCreateConnection(ConnectorViewModel source, ConnectorViewModel? target)
=> target == null || (source != target &&
- source.Shape == target.Shape &&
+ (source.Shape == target.Shape ||
+ ((source.IsCopyConnector || target.IsCopyConnector) && source.Shape != ConnectorShape.Triangle && target.Shape != ConnectorShape.Triangle)) &&
!source.IsConnected &&
!target.IsConnected &&
source.IsInput != target.IsInput &&
@@ -280,6 +287,90 @@ namespace Nodify.Calculator
sysVm.Title = "Split";
}
+
+ private void HandleCopyNodeConnected(ConnectionViewModel c)
+ {
+ // Find the COPY node's input connector
+ ConnectorViewModel copyConnector = null;
+ ConnectorViewModel sourceConnector = null;
+
+ if (c.Input.IsCopyConnector)
+ {
+ copyConnector = c.Input;
+ sourceConnector = c.Output;
+ }
+ else if (c.Output.IsCopyConnector)
+ {
+ copyConnector = c.Output;
+ sourceConnector = c.Input;
+ }
+
+ if (copyConnector == null) return;
+
+ var copyOp = copyConnector.Operation;
+ if (copyOp is not SystemOperationViewModel sysVm || sysVm.SystemOperationType != SystemOperations.COPY)
+ return;
+
+ // Adapt the input connector to match the source
+ copyConnector.Shape = sourceConnector.Shape;
+ copyConnector.ConnectorColor = sourceConnector.RawColor;
+ copyConnector.DataType = sourceConnector.DataType;
+
+ // Remove any existing outputs and their connections
+ var toRemove = copyOp.Output.ToList();
+ toRemove.ForEach(o =>
+ {
+ DisconnectConnector(o);
+ copyOp.Output.Remove(o);
+ });
+
+ // Create 2 output copy connectors matching the source
+ for (int i = 0; i < 2; i++)
+ {
+ copyOp.Output.Add(new ConnectorViewModel
+ {
+ Title = sourceConnector.Title ?? "",
+ IsInput = false,
+ Shape = sourceConnector.Shape,
+ ConnectorColor = sourceConnector.RawColor,
+ DataType = sourceConnector.DataType,
+ IsCopyConnector = true
+ });
+ }
+ }
+
+ private void HandleCopyNodeDisconnected(ConnectionViewModel c)
+ {
+ ConnectorViewModel copyConnector = null;
+
+ if (c.Input.IsCopyConnector && c.Input.IsInput)
+ copyConnector = c.Input;
+ else if (c.Output.IsCopyConnector && c.Output.IsInput)
+ copyConnector = c.Output;
+
+ if (copyConnector == null) return;
+
+ var copyOp = copyConnector.Operation;
+ if (copyOp is not SystemOperationViewModel sysVm || sysVm.SystemOperationType != SystemOperations.COPY)
+ return;
+
+ // Check if input still has a connection
+ var stillConnected = Connections.Any(con => con.Input == copyConnector || con.Output == copyConnector);
+ if (stillConnected) return;
+
+ // Reset input connector to universal
+ copyConnector.Shape = ConnectorShape.Circle;
+ copyConnector.ConnectorColor = System.Drawing.Color.White;
+ copyConnector.DataType = string.Empty;
+
+ // Remove all outputs and their connections
+ var toRemove = copyOp.Output.ToList();
+ toRemove.ForEach(o =>
+ {
+ DisconnectConnector(o);
+ copyOp.Output.Remove(o);
+ });
+ }
}
}
diff --git a/Examples/Nodify.Calculator/ConnectorViewModel.cs b/Examples/Nodify.Calculator/ConnectorViewModel.cs
index 468f2e0..169c974 100644
--- a/Examples/Nodify.Calculator/ConnectorViewModel.cs
+++ b/Examples/Nodify.Calculator/ConnectorViewModel.cs
@@ -67,6 +67,8 @@ namespace Nodify.Calculator
}
}
+ public System.Drawing.Color RawColor => _color;
+
public Brush Color
{
get
@@ -108,6 +110,16 @@ namespace Nodify.Calculator
set => SetProperty(ref _dataType, value);
}
+ private bool _isCopyConnector;
+ ///
+ /// When true, this connector belongs to a COPY node and accepts any shape for connections.
+ ///
+ public bool IsCopyConnector
+ {
+ get => _isCopyConnector;
+ set => SetProperty(ref _isCopyConnector, value);
+ }
+
///
/// Returns a color based on the C# data type.
///
diff --git a/Examples/Nodify.Calculator/Operations/OperationFactory.cs b/Examples/Nodify.Calculator/Operations/OperationFactory.cs
index f3be48a..8786028 100644
--- a/Examples/Nodify.Calculator/Operations/OperationFactory.cs
+++ b/Examples/Nodify.Calculator/Operations/OperationFactory.cs
@@ -23,12 +23,10 @@ namespace Nodify.Calculator
{
Title = "COPY",
Type = OperationType.System,
- OPType = "copy",
+ sysOp = SystemOperations.COPY,
IsFlowNode = false,
};
copynode.Input.Add("");
- copynode.Output.Add("");
- copynode.Output.Add("");
var begin = new OperationInfoViewModel()
{
@@ -330,6 +328,20 @@ namespace Nodify.Calculator
SystemOperationType = info.sysOp
};
+ if (info.sysOp == SystemOperations.COPY)
+ {
+ sysOp.Title = info.Title;
+ // Universal input — accepts any shape. Starts with no outputs.
+ foreach (var item in input)
+ {
+ item.Shape = ConnectorShape.Circle;
+ item.ConnectorColor = Color.White;
+ item.IsCopyConnector = true;
+ sysOp.Input.Add(item);
+ }
+ return sysOp;
+ }
+
if (info.sysOp == SystemOperations.SPLIT)
{
sysOp.Title = info.Title;