diff --git a/src/Artemis.UI/Screens/VisualScripting/NodeViewModel.cs b/src/Artemis.UI/Screens/VisualScripting/NodeViewModel.cs index 5631a38be..bb3a1670d 100644 --- a/src/Artemis.UI/Screens/VisualScripting/NodeViewModel.cs +++ b/src/Artemis.UI/Screens/VisualScripting/NodeViewModel.cs @@ -1,5 +1,6 @@ using System; using System.Collections.ObjectModel; +using System.Linq; using System.Reactive; using System.Reactive.Linq; using Artemis.Core; @@ -41,44 +42,23 @@ public class NodeViewModel : ActivatableViewModelBase DeleteNode = ReactiveCommand.Create(ExecuteDeleteNode, this.WhenAnyValue(vm => vm.IsStaticNode).Select(v => !v)); - SourceList nodePins = new(); - SourceList nodePinCollections = new(); + SourceList nodePins = new(); + SourceList nodePinCollections = new(); // Create observable collections split up by direction - nodePins.Connect() - .Filter(n => n.Direction == PinDirection.Input) - .Transform(p => (PinViewModel) nodeVmFactory.InputPinViewModel(p, nodeScriptViewModel)) - .Bind(out ReadOnlyObservableCollection inputPins) - .Subscribe(); - nodePins.Connect() - .Filter(n => n.Direction == PinDirection.Output) - .Transform(p => (PinViewModel) nodeVmFactory.OutputPinViewModel(p, nodeScriptViewModel)) - .Bind(out ReadOnlyObservableCollection outputPins) - .Subscribe(); + nodePins.Connect().Filter(n => n.Pin.Direction == PinDirection.Input).Bind(out ReadOnlyObservableCollection inputPins).Subscribe(); + nodePins.Connect().Filter(n => n.Pin.Direction == PinDirection.Output).Bind(out ReadOnlyObservableCollection outputPins).Subscribe(); InputPinViewModels = inputPins; OutputPinViewModels = outputPins; // Same again but for pin collections - nodePinCollections.Connect() - .Filter(n => n.Direction == PinDirection.Input) - .Transform(c => (PinCollectionViewModel) nodeVmFactory.InputPinCollectionViewModel(c, nodeScriptViewModel)) - .Bind(out ReadOnlyObservableCollection inputPinCollections) - .Subscribe(); - nodePinCollections.Connect() - .Filter(n => n.Direction == PinDirection.Output) - .Transform(c => (PinCollectionViewModel) nodeVmFactory.OutputPinCollectionViewModel(c, nodeScriptViewModel)) - .Bind(out ReadOnlyObservableCollection outputPinCollections) - .Subscribe(); + nodePinCollections.Connect().Filter(n => n.PinCollection.Direction == PinDirection.Input).Bind(out ReadOnlyObservableCollection inputPinCollections).Subscribe(); + nodePinCollections.Connect().Filter(n => n.PinCollection.Direction == PinDirection.Output).Bind(out ReadOnlyObservableCollection outputPinCollections).Subscribe(); InputPinCollectionViewModels = inputPinCollections; OutputPinCollectionViewModels = outputPinCollections; // Create a single observable collection containing all pin view models - InputPinViewModels.ToObservableChangeSet() - .Merge(OutputPinViewModels.ToObservableChangeSet()) - .Merge(InputPinCollectionViewModels.ToObservableChangeSet().TransformMany(c => c.PinViewModels)) - .Merge(OutputPinCollectionViewModels.ToObservableChangeSet().TransformMany(c => c.PinViewModels)) - .Bind(out ReadOnlyObservableCollection pins) - .Subscribe(); + nodePins.Connect().Merge(nodePinCollections.Connect().TransformMany(c => c.PinViewModels)).Bind(out ReadOnlyObservableCollection pins).Subscribe(); PinViewModels = pins; @@ -101,30 +81,54 @@ public class NodeViewModel : ActivatableViewModelBase // Subscribe to pin changes Observable.FromEventPattern>(x => Node.PinAdded += x, x => Node.PinAdded -= x) - .Subscribe(p => nodePins.Add(p.EventArgs.Value)) + .Subscribe(p => + { + if (p.EventArgs.Value.Direction == PinDirection.Input) + nodePins.Add(nodeVmFactory.InputPinViewModel(p.EventArgs.Value, nodeScriptViewModel)); + else + nodePins.Add(nodeVmFactory.OutputPinViewModel(p.EventArgs.Value, nodeScriptViewModel)); + }) .DisposeWith(d); Observable.FromEventPattern>(x => Node.PinRemoved += x, x => Node.PinRemoved -= x) - .Subscribe(p => nodePins.Remove(p.EventArgs.Value)) + .Subscribe(p => nodePins!.Remove(nodePins.Items.FirstOrDefault(vm => vm.Pin == p.EventArgs.Value))) .DisposeWith(d); nodePins.Edit(l => { l.Clear(); - l.AddRange(Node.Pins); + foreach (IPin nodePin in Node.Pins) + { + if (nodePin.Direction == PinDirection.Input) + l.Add(nodeVmFactory.InputPinViewModel(nodePin, nodeScriptViewModel)); + else + l.Add(nodeVmFactory.OutputPinViewModel(nodePin, nodeScriptViewModel)); + } }); - + // Subscribe to pin collection changes Observable.FromEventPattern>(x => Node.PinCollectionAdded += x, x => Node.PinCollectionAdded -= x) - .Subscribe(p => nodePinCollections.Add(p.EventArgs.Value)) + .Subscribe(p => + { + if (p.EventArgs.Value.Direction == PinDirection.Input) + nodeVmFactory.InputPinCollectionViewModel(p.EventArgs.Value, nodeScriptViewModel); + else + nodeVmFactory.OutputPinCollectionViewModel(p.EventArgs.Value, nodeScriptViewModel); + }) .DisposeWith(d); Observable.FromEventPattern>(x => Node.PinCollectionRemoved += x, x => Node.PinCollectionRemoved -= x) - .Subscribe(p => nodePinCollections.Remove(p.EventArgs.Value)) + .Subscribe(p => nodePinCollections!.Remove(nodePinCollections.Items.FirstOrDefault(vm => vm.PinCollection == p.EventArgs.Value))) .DisposeWith(d); nodePinCollections.Edit(l => { l.Clear(); - l.AddRange(Node.PinCollections); + foreach (IPinCollection nodePinCollection in Node.PinCollections) + { + if (nodePinCollection.Direction == PinDirection.Input) + l.Add(nodeVmFactory.InputPinCollectionViewModel(nodePinCollection, nodeScriptViewModel)); + else + l.Add(nodeVmFactory.OutputPinCollectionViewModel(nodePinCollection, nodeScriptViewModel)); + } }); - + if (Node is Node coreNode) CustomNodeViewModel = coreNode.GetCustomViewModel(nodeScriptViewModel.NodeScript); }); diff --git a/src/Artemis.VisualScripting/Nodes/Branching/EnumSwitchNode.cs b/src/Artemis.VisualScripting/Nodes/Branching/EnumSwitchNode.cs new file mode 100644 index 000000000..f94911661 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Branching/EnumSwitchNode.cs @@ -0,0 +1,97 @@ +using Artemis.Core; +using Artemis.Core.Events; +using Humanizer; + +namespace Artemis.VisualScripting.Nodes.Branching; + +[Node("Switch (Enum)", "Outputs the input that corresponds to the switch value", "Operators", InputType = typeof(Enum), OutputType = typeof(object))] +public class EnumSwitchNode : Node +{ + private readonly Dictionary _inputPins; + + public EnumSwitchNode() : base("Enum Branch", "desc") + { + _inputPins = new Dictionary(); + + Output = CreateOutputPin(typeof(object), "Result"); + SwitchValue = CreateInputPin("Switch"); + + SwitchValue.PinConnected += OnSwitchPinConnected; + SwitchValue.PinDisconnected += OnSwitchPinDisconnected; + } + + public OutputPin Output { get; } + public InputPin SwitchValue { get; } + + public override void Evaluate() + { + if (SwitchValue.Value is null) + { + Output.Value = null; + return; + } + + if (!_inputPins.TryGetValue(SwitchValue.Value, out InputPin? pin)) + { + Output.Value = null; + return; + } + + if (pin.ConnectedTo.Count == 0) + { + Output.Value = null; + return; + } + + Output.Value = pin.Value; + } + + private void OnInputPinDisconnected(object? sender, SingleValueEventArgs e) + { + // if this is the last pin to disconnect, reset the type. + if (_inputPins.Values.All(i => i.ConnectedTo.Count == 0)) + ChangeType(typeof(object)); + } + + private void OnInputPinConnected(object? sender, SingleValueEventArgs e) + { + // change the type of our inputs and output + // depending on the first node the user connects to + ChangeType(e.Value.Type); + } + + private void OnSwitchPinConnected(object? sender, SingleValueEventArgs e) + { + if (SwitchValue.ConnectedTo.Count == 0) + return; + + Type enumType = SwitchValue.ConnectedTo[0].Type; + foreach (Enum enumValue in Enum.GetValues(enumType).Cast()) + { + InputPin pin = CreateOrAddInputPin(typeof(object), enumValue.ToString().Humanize(LetterCasing.Sentence)); + pin.PinConnected += OnInputPinConnected; + pin.PinDisconnected += OnInputPinDisconnected; + _inputPins[enumValue] = pin; + } + } + + private void OnSwitchPinDisconnected(object? sender, SingleValueEventArgs e) + { + foreach (InputPin input in _inputPins.Values) + { + input.PinConnected -= OnInputPinConnected; + input.PinDisconnected -= OnInputPinDisconnected; + RemovePin(input); + } + + _inputPins.Clear(); + ChangeType(typeof(object)); + } + + private void ChangeType(Type type) + { + foreach (InputPin input in _inputPins.Values) + input.ChangeType(type); + Output.ChangeType(type); + } +} \ No newline at end of file