using System; using System.Collections.Generic; using System.Linq; using System.Reactive.Linq; using DynamicData; using NodeNetwork.Toolkit.ValueNode; using NodeNetwork.ViewModels; using ReactiveUI; namespace NodeNetwork.Toolkit.Group { /// /// Basic reference implementation of NodeGroupIOBinding for ValueInputViewModels and ValueOutputViewModels. /// public class ValueNodeGroupIOBinding : NodeGroupIOBinding { private readonly IDictionary _outputInputMapping = new Dictionary(); public ValueNodeGroupIOBinding(NodeViewModel groupNode, NodeViewModel entranceNode, NodeViewModel exitNode) : base(groupNode, entranceNode, exitNode) { // For each input on the group node, create an output in the subnet groupNode.Inputs.Connect() .Filter(input => input.PortPosition == PortPosition.Left) .Transform(i => { // Dynamic is applied here so that late binding is used to find the most specific // CreateCompatibleOutput variant for this specific input. NodeOutputViewModel result = CreateCompatibleOutput((dynamic)i); BindOutputToInput((dynamic)result, (dynamic)i); return result; }).PopulateInto(entranceNode.Outputs); groupNode.Inputs.Connect() .Filter(input => input.PortPosition == PortPosition.Right) .Transform(i => { NodeOutputViewModel result = CreateCompatibleOutput((dynamic) i); BindOutputToInput((dynamic) result, (dynamic) i); return result; }).PopulateInto(exitNode.Outputs); groupNode.Inputs.Connect().OnItemRemoved(input => _outputInputMapping.Remove( _outputInputMapping.First(kvp => kvp.Value == input) ) ); // For each output on the group node, create an input in the subnet groupNode.Outputs.Connect() .Filter(input => input.PortPosition == PortPosition.Right) .Transform(o => { NodeInputViewModel result = CreateCompatibleInput((dynamic)o); BindOutputToInput((dynamic)o, (dynamic)result); return result; }).PopulateInto(exitNode.Inputs); groupNode.Outputs.Connect() .Filter(input => input.PortPosition == PortPosition.Left) .Transform(o => { NodeInputViewModel result = CreateCompatibleInput((dynamic)o); BindOutputToInput((dynamic)o, (dynamic)result); return result; }).PopulateInto(entranceNode.Inputs); groupNode.Outputs.Connect().OnItemRemoved(output => _outputInputMapping.Remove(output)); } protected virtual void BindEndpointProperties(NodeOutputViewModel output, NodeInputViewModel input) { input.WhenAnyValue(vm => vm.Name).BindTo(output, vm => vm.Name); output.WhenAnyValue(vm => vm.Name).BindTo(input, vm => vm.Name); input.WhenAnyValue(vm => vm.SortIndex).BindTo(output, vm => vm.SortIndex); output.WhenAnyValue(vm => vm.SortIndex).BindTo(input, vm => vm.SortIndex); input.WhenAnyValue(vm => vm.Icon).BindTo(output, vm => vm.Icon); output.WhenAnyValue(vm => vm.Icon).BindTo(input, vm => vm.Icon); } protected virtual void BindOutputToInput(ValueNodeOutputViewModel output, ValueNodeInputViewModel input) { BindEndpointProperties(output, input); output.Value = input.ValueChanged; _outputInputMapping.Add(output, input); } protected virtual void BindOutputToInput(ValueNodeOutputViewModel> output, ValueListNodeInputViewModel input) { BindEndpointProperties(output, input); output.Value = Observable.Return(input.Values); _outputInputMapping.Add(output, input); } #region Endpoint Create public virtual ValueNodeOutputViewModel CreateCompatibleOutput(ValueNodeInputViewModel input) { return new ValueNodeOutputViewModel() { Name = input.Name, Icon = input.Icon }; } public virtual ValueNodeOutputViewModel> CreateCompatibleOutput(ValueListNodeInputViewModel input) { return new ValueNodeOutputViewModel>(); } public virtual ValueNodeInputViewModel CreateCompatibleInput(ValueNodeOutputViewModel output) { return new ValueNodeInputViewModel() { Name = output.Name, Icon = output.Icon }; } public virtual ValueListNodeInputViewModel CreateCompatibleInput(ValueNodeOutputViewModel> output) { return new ValueListNodeInputViewModel() { Name = output.Name, Icon = output.Icon }; } #endregion #region Endpoint Add public override NodeInputViewModel AddNewGroupNodeInput(NodeOutputViewModel candidateOutput) { NodeInputViewModel input = CreateCompatibleInput((dynamic)candidateOutput); input.Name = candidateOutput.Name; GroupNode.Inputs.Add(input); // Append to bottom of list input.SortIndex = GroupNode.Inputs.Items.Select(i => i.SortIndex).DefaultIfEmpty(-1).Max() + 1; return input; } public override NodeOutputViewModel AddNewSubnetInlet(NodeInputViewModel candidateInput) { NodeInputViewModel input = AddNewGroupNodeInput(CreateCompatibleOutput((dynamic)candidateInput)); return GetSubnetInlet(input); } public override NodeInputViewModel AddNewSubnetOutlet(NodeOutputViewModel candidateOutput) { NodeOutputViewModel output = AddNewGroupNodeOutput(CreateCompatibleInput((dynamic)candidateOutput)); return GetSubnetOutlet(output); } public override NodeOutputViewModel AddNewGroupNodeOutput(NodeInputViewModel candidateInput) { NodeOutputViewModel output = CreateCompatibleOutput((dynamic)candidateInput); output.Name = candidateInput.Name; GroupNode.Outputs.Add(output); // Append to bottom of list output.SortIndex = GroupNode.Outputs.Items.Select(o => o.SortIndex).DefaultIfEmpty(-1).Max() + 1; return output; } #endregion #region Endpoint Getters public override NodeInputViewModel GetGroupNodeInput(NodeOutputViewModel entranceOutput) { return _outputInputMapping[entranceOutput]; } public override NodeOutputViewModel GetSubnetInlet(NodeInputViewModel entranceInput) { return _outputInputMapping.Single(p => p.Value == entranceInput).Key; } public override NodeInputViewModel GetSubnetOutlet(NodeOutputViewModel exitOutput) { return _outputInputMapping[exitOutput]; } public override NodeOutputViewModel GetGroupNodeOutput(NodeInputViewModel exitInput) { return _outputInputMapping.Single(p => p.Value == exitInput).Key; } #endregion /// /// Remove an endpoint, which can be from group node, entrance node or exit node. /// Also removes the corresponding endpoint in the other network. /// /// Input or output to be removed. public virtual void DeleteEndpoint(Endpoint endpoint) { // Because the subnet entrance and exit are derived from the groupnode, // endpoints should be deleted from the groupnode only. bool isOnGroupNode = GroupNode == endpoint.Parent; if (endpoint is NodeInputViewModel input) { if (isOnGroupNode) { GroupNode.Inputs.Remove(input); } else { var groupOutput = GetGroupNodeOutput(input); GroupNode.Outputs.Remove(groupOutput); } } else if(endpoint is NodeOutputViewModel output) { if (isOnGroupNode) { GroupNode.Outputs.Remove(output); } else { var groupInput = GetGroupNodeInput(output); GroupNode.Inputs.Remove(groupInput); } } } } }