diff --git a/Examples/Nodify.Calculator/CalculatorViewModel.cs b/Examples/Nodify.Calculator/CalculatorViewModel.cs index c47c75b..9f3b14d 100644 --- a/Examples/Nodify.Calculator/CalculatorViewModel.cs +++ b/Examples/Nodify.Calculator/CalculatorViewModel.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.IO; using System.Linq; using System.Windows; @@ -24,6 +25,9 @@ namespace Nodify.Calculator c.Input.Value = c.Output.Value; c.Output.ValueObservers.Add(c.Input); + + // Dynamic Split node: populate outputs when a model connects to the Square input + HandleSplitNodeConnected(c); }) .WhenRemoved(c => { @@ -41,6 +45,9 @@ namespace Nodify.Calculator } c.Output.ValueObservers.Remove(c.Input); + + // Dynamic Split node: clear outputs when model disconnects + HandleSplitNodeDisconnected(c); }); Operations.WhenAdded(x => @@ -112,7 +119,16 @@ namespace Nodify.Calculator source.Shape == target.Shape && !source.IsConnected && !target.IsConnected && - source.IsInput != target.IsInput); + source.IsInput != target.IsInput && + AreDataTypesCompatible(source.DataType, target.DataType)); + + private static bool AreDataTypesCompatible(string sourceType, string targetType) + { + // If either side has no DataType set, allow connection (generic/untyped connector) + if (string.IsNullOrEmpty(sourceType) || string.IsNullOrEmpty(targetType)) + return true; + return string.Equals(sourceType, targetType, System.StringComparison.OrdinalIgnoreCase); + } internal void OpenGetSetVariable(Point TargetLocation, string className) { @@ -179,6 +195,91 @@ namespace Nodify.Calculator GroupSize = new Size(bounding.Width, bounding.Height) }); } + + private void HandleSplitNodeConnected(ConnectionViewModel c) + { + // Determine which side is the Split node's input (Square connector) + var splitInput = c.Input.Shape == ConnectorShape.Square ? c.Input : null; + if (splitInput == null) splitInput = c.Output.Shape == ConnectorShape.Square ? c.Output : null; + if (splitInput == null) return; + + var splitOp = splitInput.Operation; + if (splitOp is not SystemOperationViewModel sysVm || sysVm.SystemOperationType != SystemOperations.SPLIT) + return; + + // The other end is the source — find which model class it comes from + var sourceConnector = (c.Input == splitInput) ? c.Output : c.Input; + var sourceOp = sourceConnector.Operation; + + // Determine class name from the source operation title (e.g. "GET ClassName" or "SET ClassName") + string className = null; + if (sourceOp is SystemOperationViewModel srcSys) + { + var title = srcSys.Title ?? ""; + if (title.StartsWith("GET ")) className = title.Substring(4).Trim(); + else if (title.StartsWith("SET ")) className = title.Split(' ').ElementAtOrDefault(1); + } + + if (string.IsNullOrEmpty(className)) return; + + // Read model .cs file and parse properties + var customModelDir = "CustomModels"; + var filePath = Path.Combine(customModelDir, $"{className}.cs"); + if (!File.Exists(filePath)) return; + + var fileContent = File.ReadAllText(filePath); + var properties = OperationFactory.GetPropertiesFromClassPublic(fileContent); + if (properties == null || properties.Count == 0) return; + + // Remove any existing dynamic outputs (except flow connectors) + var toRemove = splitOp.Output.Where(o => o.Shape != ConnectorShape.Triangle).ToList(); + toRemove.ForEach(o => + { + DisconnectConnector(o); + splitOp.Output.Remove(o); + }); + + // Add property outputs + foreach (var prop in properties) + { + var propColor = ConnectorViewModel.GetColorForType(prop.Type); + splitOp.Output.Add(new ConnectorViewModel + { + Title = $"{prop.Name} ({prop.Type})", + IsInput = false, + Shape = ConnectorShape.Circle, + ConnectorColor = propColor, + DataType = prop.Type.ToLower() + }); + } + + sysVm.Title = $"Split {className}"; + } + + private void HandleSplitNodeDisconnected(ConnectionViewModel c) + { + var splitInput = c.Input.Shape == ConnectorShape.Square ? c.Input : null; + if (splitInput == null) splitInput = c.Output.Shape == ConnectorShape.Square ? c.Output : null; + if (splitInput == null) return; + + var splitOp = splitInput.Operation; + if (splitOp is not SystemOperationViewModel sysVm || sysVm.SystemOperationType != SystemOperations.SPLIT) + return; + + // Check if the Square input still has any connection + var stillConnected = Connections.Any(con => con.Input == splitInput || con.Output == splitInput); + if (stillConnected) return; + + // Remove all dynamic output connectors and their connections + var toRemove = splitOp.Output.Where(o => o.Shape != ConnectorShape.Triangle).ToList(); + toRemove.ForEach(o => + { + DisconnectConnector(o); + splitOp.Output.Remove(o); + }); + + sysVm.Title = "Split"; + } } } diff --git a/Examples/Nodify.Calculator/ConnectorViewModel.cs b/Examples/Nodify.Calculator/ConnectorViewModel.cs index 05b44f1..468f2e0 100644 --- a/Examples/Nodify.Calculator/ConnectorViewModel.cs +++ b/Examples/Nodify.Calculator/ConnectorViewModel.cs @@ -96,5 +96,37 @@ namespace Nodify.Calculator [Newtonsoft.Json.JsonIgnore] [BsonIgnore] public List ValueObservers { get; } = new List(); + + private string _dataType = string.Empty; + /// + /// The underlying C# type of this connector (e.g. "string", "int", "double"). + /// Used to enforce type-safe connections. + /// + public string DataType + { + get => _dataType; + set => SetProperty(ref _dataType, value); + } + + /// + /// Returns a color based on the C# data type. + /// + public static System.Drawing.Color GetColorForType(string typeName) + { + return (typeName ?? "").ToLower() switch + { + "string" => System.Drawing.Color.CornflowerBlue, + "int" => System.Drawing.Color.LightGreen, + "double" => System.Drawing.Color.Orange, + "float" => System.Drawing.Color.Gold, + "bool" => System.Drawing.Color.Tomato, + "decimal" => System.Drawing.Color.MediumOrchid, + "long" => System.Drawing.Color.YellowGreen, + "datetime" => System.Drawing.Color.DeepSkyBlue, + "object" => System.Drawing.Color.Silver, + "list" => System.Drawing.Color.Plum, + _ => System.Drawing.Color.LightGray, + }; + } } } diff --git a/Examples/Nodify.Calculator/CreateModelDialog.xaml b/Examples/Nodify.Calculator/CreateModelDialog.xaml new file mode 100644 index 0000000..e295846 --- /dev/null +++ b/Examples/Nodify.Calculator/CreateModelDialog.xaml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + +