From 36f996eb1a5bc74ea8a665b03fb8f94124e0bc31 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sun, 21 Aug 2022 23:51:01 +0200 Subject: [PATCH] Added some nodes --- .../Nodes/Color/LerpSKColorNode.cs | 57 ++++++++ .../Nodes/Color/RgbSKColorNode.cs | 38 +++++ .../Nodes/Mathematics/Clamp.cs | 39 +++++ .../Nodes/Mathematics/LerpNode.cs | 45 ++++++ .../Nodes/Mathematics/RangeNode.cs | 48 +++++++ .../Nodes/Mathematics/Saturate.cs | 35 +++++ .../Nodes/Static/RandomNumericValueNode.cs | 32 +++++ .../Nodes/Timing/DelayNode.cs | 117 +++++++++++++++ .../Nodes/Timing/EdgeNode.cs | 41 ++++++ .../Nodes/Timing/FlipFlopNode.cs | 45 ++++++ .../Nodes/Timing/LatchNode.cs | 113 +++++++++++++++ .../Nodes/Timing/SequencerNode.cs | 136 ++++++++++++++++++ 12 files changed, 746 insertions(+) create mode 100644 src/Artemis.VisualScripting/Nodes/Color/LerpSKColorNode.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Color/RgbSKColorNode.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Mathematics/Clamp.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Mathematics/LerpNode.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Mathematics/RangeNode.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Mathematics/Saturate.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Static/RandomNumericValueNode.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Timing/DelayNode.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Timing/EdgeNode.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Timing/FlipFlopNode.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Timing/LatchNode.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Timing/SequencerNode.cs diff --git a/src/Artemis.VisualScripting/Nodes/Color/LerpSKColorNode.cs b/src/Artemis.VisualScripting/Nodes/Color/LerpSKColorNode.cs new file mode 100644 index 000000000..0ca5125e0 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Color/LerpSKColorNode.cs @@ -0,0 +1,57 @@ +using Artemis.Core; +using RGB.NET.Core; +using SkiaSharp; + +namespace Artemis.VisualScripting.Nodes.Color; + +[Node("Lerp (Color)", "Interpolates linear between the two colors A and B", "Color", InputType = typeof(SKColor), OutputType = typeof(SKColor))] +public class LerpSKColorNode : Node +{ + #region Properties & Fields + + public InputPin A { get; } + public InputPin B { get; } + public InputPin T { get; } + + public OutputPin Result { get; } + + #endregion + + #region Constructors + + public LerpSKColorNode() + : base("Lerp", "Interpolates linear between the two values A and B") + { + A = CreateInputPin("A"); + B = CreateInputPin("B"); + T = CreateInputPin("T"); + + Result = CreateOutputPin(); + } + + #endregion + + #region Methods + + /// + public override void Evaluate() + { + SKColor a = A.Value; + SKColor b = B.Value; + float t = ((float)T.Value).Clamp(0f, 1f); + + float aAlpha = a.Alpha.GetPercentageFromByteValue(); + float aRed = a.Red.GetPercentageFromByteValue(); + float aGreen = a.Green.GetPercentageFromByteValue(); + float aBlue = a.Blue.GetPercentageFromByteValue(); + + float alpha = ((b.Alpha.GetPercentageFromByteValue() - aAlpha) * t) + aAlpha; + float red = ((b.Red.GetPercentageFromByteValue() - aRed) * t) + aRed; + float green = ((b.Green.GetPercentageFromByteValue() - aGreen) * t) + aGreen; + float blue = ((b.Blue.GetPercentageFromByteValue() - aBlue) * t) + aBlue; + + Result.Value = new SKColor(red.GetByteValueFromPercentage(), green.GetByteValueFromPercentage(), blue.GetByteValueFromPercentage(), alpha.GetByteValueFromPercentage()); + } + + #endregion +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Color/RgbSKColorNode.cs b/src/Artemis.VisualScripting/Nodes/Color/RgbSKColorNode.cs new file mode 100644 index 000000000..338ab17da --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Color/RgbSKColorNode.cs @@ -0,0 +1,38 @@ +using Artemis.Core; +using SkiaSharp; + +namespace Artemis.VisualScripting.Nodes.Color; + +[Node("RGB Color", "Creates a color from red, green and blue values", "Color", InputType = typeof(Numeric), OutputType = typeof(SKColor))] +public class RgbSKColorNode : Node +{ + #region Properties & Fields + + public InputPin R { get; set; } + public InputPin G { get; set; } + public InputPin B { get; set; } + public OutputPin Output { get; } + + #endregion + + #region Constructors + + public RgbSKColorNode() + : base("RGB Color", "Creates a color from red, green and blue values") + { + R = CreateInputPin("R"); + G = CreateInputPin("G"); + B = CreateInputPin("B"); + + Output = CreateOutputPin(); + } + + #endregion + + #region Methods + + /// + public override void Evaluate() => Output.Value = new SKColor(R.Value, G.Value, B.Value); + + #endregion +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Mathematics/Clamp.cs b/src/Artemis.VisualScripting/Nodes/Mathematics/Clamp.cs new file mode 100644 index 000000000..2fcb944f1 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Mathematics/Clamp.cs @@ -0,0 +1,39 @@ +using Artemis.Core; +using RGB.NET.Core; + +namespace Artemis.VisualScripting.Nodes.Mathematics; + +[Node("Clamp", "Clamps the value to be in between min and max", "Mathematics", InputType = typeof(Numeric), OutputType = typeof(Numeric))] +public class ClampNode : Node +{ + #region Properties & Fields + + public InputPin Value { get; } + public InputPin Min { get; } + public InputPin Max { get; } + + public OutputPin Result { get; } + + #endregion + + #region Constructors + + public ClampNode() + : base("Clamp", "Clamps the value to be in between min and max") + { + Value = CreateInputPin("Value"); + Min = CreateInputPin("Min"); + Max = CreateInputPin("Max"); + + Result = CreateOutputPin(); + } + + #endregion + + #region Methods + + /// + public override void Evaluate() => Result.Value = ((float)Value.Value).Clamp(Min.Value, Max.Value); + + #endregion +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Mathematics/LerpNode.cs b/src/Artemis.VisualScripting/Nodes/Mathematics/LerpNode.cs new file mode 100644 index 000000000..23273208c --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Mathematics/LerpNode.cs @@ -0,0 +1,45 @@ +using Artemis.Core; +using RGB.NET.Core; + +namespace Artemis.VisualScripting.Nodes.Mathematics; + +[Node("Lerp", "Interpolates linear between the two values A and B", "Mathematics", InputType = typeof(Numeric), OutputType = typeof(Numeric))] +public class LerpNode : Node +{ + #region Properties & Fields + + public InputPin A { get; } + public InputPin B { get; } + public InputPin T { get; } + + public OutputPin Result { get; } + + #endregion + + #region Constructors + + public LerpNode() + : base("Lerp", "Interpolates linear between the two values A and B") + { + A = CreateInputPin("A"); + B = CreateInputPin("B"); + T = CreateInputPin("T"); + + Result = CreateOutputPin(); + } + + #endregion + + #region Methods + + /// + public override void Evaluate() + { + float a = A.Value; + float b = B.Value; + float t = ((float)T.Value).Clamp(0f, 1f); + Result.Value = ((b - a) * t) + a; + } + + #endregion +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Mathematics/RangeNode.cs b/src/Artemis.VisualScripting/Nodes/Mathematics/RangeNode.cs new file mode 100644 index 000000000..74e6cc50d --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Mathematics/RangeNode.cs @@ -0,0 +1,48 @@ +using Artemis.Core; +using RGB.NET.Core; + +namespace Artemis.VisualScripting.Nodes.Mathematics; + +[Node("Range", "Selects the best integer value in the given range by the given percentage", "Static", InputType = typeof(Numeric), OutputType = typeof(Numeric))] +public class RangeNode : Node +{ + #region Properties & Fields + + public InputPin Min { get; } + public InputPin Max { get; } + public InputPin Percentage { get; } + + public OutputPin Result { get; } + + #endregion + + #region Constructors + + public RangeNode() + : base("Range", "Selects the best integer value in the given range by the given percentage") + { + Min = CreateInputPin("Min"); + Max = CreateInputPin("Max"); + Percentage = CreateInputPin("Percentage"); + + Result = CreateOutputPin(); + } + + #endregion + + #region Methods + + /// + public override void Evaluate() + { + int min = Min.Value; + int max = Max.Value; + float percentage = ((float)Percentage.Value).Clamp(0f, 1f); + + int range = max - min; + + Result.Value = percentage >= 1.0f ? max : ((int)(percentage * (range + 1)) + min); + } + + #endregion +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Mathematics/Saturate.cs b/src/Artemis.VisualScripting/Nodes/Mathematics/Saturate.cs new file mode 100644 index 000000000..5c75da96b --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Mathematics/Saturate.cs @@ -0,0 +1,35 @@ +using Artemis.Core; +using RGB.NET.Core; + +namespace Artemis.VisualScripting.Nodes.Mathematics; + +[Node("Saturate", "Clamps the value to be in between 0 and 1", "Mathematics", InputType = typeof(Numeric), OutputType = typeof(Numeric))] +public class SaturateNode : Node +{ + #region Properties & Fields + + public InputPin Value { get; } + + public OutputPin Result { get; } + + #endregion + + #region Constructors + + public SaturateNode() + : base("Clamp", "Clamps the value to be in between 0 and 1") + { + Value = CreateInputPin(); + + Result = CreateOutputPin(); + } + + #endregion + + #region Methods + + /// + public override void Evaluate() => Result.Value = ((float)Value.Value).Clamp(0f, 1f); + + #endregion +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Static/RandomNumericValueNode.cs b/src/Artemis.VisualScripting/Nodes/Static/RandomNumericValueNode.cs new file mode 100644 index 000000000..a883d0dcb --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Static/RandomNumericValueNode.cs @@ -0,0 +1,32 @@ +using Artemis.Core; + +namespace Artemis.VisualScripting.Nodes.Static; + +[Node("Random", "Generates a random value between 0 and 1", "Static", OutputType = typeof(Numeric))] +public class RandomNumericValueNode : Node +{ + #region Properties & Fields + + private static readonly Random RANDOM = new(); + + public OutputPin Output { get; } + + #endregion + + #region Constructors + + public RandomNumericValueNode() + : base("Random", "Generates a random value between 0 and 1") + { + Output = CreateOutputPin(); + } + + #endregion + + #region Methods + + /// + public override void Evaluate() => Output.Value = RANDOM.NextSingle(); + + #endregion +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Timing/DelayNode.cs b/src/Artemis.VisualScripting/Nodes/Timing/DelayNode.cs new file mode 100644 index 000000000..2dee0a8e1 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Timing/DelayNode.cs @@ -0,0 +1,117 @@ +using System.Diagnostics; +using Artemis.Core; +using Artemis.Core.Events; +using RGB.NET.Core; + +namespace Artemis.VisualScripting.Nodes.Timing; + +[Node("Delay", "Delays the resolution of the input pin(s) for the given time after each update", "Timing", InputType = typeof(object), OutputType = typeof(object))] +public class DelayNode : Node +{ + #region Properties & Fields + + private long _lastUpdateTimestamp = 0; + + public InputPin Delay { get; } + public InputPinCollection Input { get; } + + public OutputPin IsUpdated { get; } + public OutputPin NextUpdateTime { get; } + + private Dictionary _pinPairs = new(); + + #endregion + + #region Constructors + + public DelayNode() + : base("Delay", "Delays the resolution of the input pin(s) for the given time after each update") + { + Delay = CreateInputPin("Delay"); + Input = CreateInputPinCollection(typeof(object), initialCount: 0); + + IsUpdated = CreateOutputPin("Updated"); + NextUpdateTime = CreateOutputPin("Next Update"); + + Input.PinAdded += OnInputPinAdded; + Input.PinRemoved += OnInputPinRemoved; + + Input.Add(Input.CreatePin()); + } + + #endregion + + #region Methods + + private void OnInputPinAdded(object? sender, SingleValueEventArgs args) + { + IPin inputPin = args.Value; + _pinPairs.Add(inputPin, CreateOutputPin(typeof(object))); + + inputPin.PinConnected += OnInputPinConnected; + inputPin.PinDisconnected += OnInputPinDisconnected; + + UpdatePinNames(); + } + + private void OnInputPinRemoved(object? sender, SingleValueEventArgs args) + { + IPin inputPin = args.Value; + RemovePin(_pinPairs[inputPin]); + _pinPairs.Remove(inputPin); + + inputPin.PinConnected -= OnInputPinConnected; + inputPin.PinDisconnected -= OnInputPinDisconnected; + + UpdatePinNames(); + } + + private void OnInputPinConnected(object? sender, SingleValueEventArgs args) + { + if (sender is not IPin inputPin || !_pinPairs.ContainsKey(inputPin)) return; + + OutputPin outputPin = _pinPairs[inputPin]; + outputPin.ChangeType(args.Value.Type); + } + + private void OnInputPinDisconnected(object? sender, SingleValueEventArgs args) + { + if (sender is not IPin inputPin || !_pinPairs.ContainsKey(inputPin)) return; + + OutputPin outputPin = _pinPairs[inputPin]; + outputPin.ChangeType(typeof(object)); + } + + private void UpdatePinNames() + { + int counter = 1; + foreach (IPin inputPin in Input.Pins) + { + string name = counter.ToString(); + inputPin.Name = name; + _pinPairs[inputPin].Name = name; + + counter++; + } + } + + /// + public override void Evaluate() + { + double nextUpdateIn = Delay.Value - TimerHelper.GetElapsedTime(_lastUpdateTimestamp); + NextUpdateTime.Value = nextUpdateIn; + + if (nextUpdateIn <= 0) + { + IsUpdated.Value = true; + foreach ((IPin input, OutputPin output) in _pinPairs) + output.Value = input.PinValue; + + _lastUpdateTimestamp = Stopwatch.GetTimestamp(); + } + else + IsUpdated.Value = false; + } + + #endregion +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Timing/EdgeNode.cs b/src/Artemis.VisualScripting/Nodes/Timing/EdgeNode.cs new file mode 100644 index 000000000..cf019289e --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Timing/EdgeNode.cs @@ -0,0 +1,41 @@ +using Artemis.Core; + +namespace Artemis.VisualScripting.Nodes.Timing; + +[Node("Edge", "Outputs true on each edge when the input changes", "Timing", InputType = typeof(bool), OutputType = typeof(bool))] +public class EdgeNode : Node +{ + #region Properties & Fields + + private bool _lastInput; + + public InputPin Input { get; } + public OutputPin Output { get; } + + #endregion + + #region Constructors + + public EdgeNode() + : base("Edge", "Outputs true on each edge when the input changes") + { + Input = CreateInputPin(); + Output = CreateOutputPin(); + } + + #endregion + + #region Methods + + /// + public override void Evaluate() + { + bool input = Input.Value; + + Output.Value = input != _lastInput; + + _lastInput = input; + } + + #endregion +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Timing/FlipFlopNode.cs b/src/Artemis.VisualScripting/Nodes/Timing/FlipFlopNode.cs new file mode 100644 index 000000000..62b6c8f21 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Timing/FlipFlopNode.cs @@ -0,0 +1,45 @@ +using Artemis.Core; + +namespace Artemis.VisualScripting.Nodes.Timing; + +[Node("FlipFlop", "Inverts the output when the input changes from false to true", "Timing", InputType = typeof(bool), OutputType = typeof(bool))] +public class FlipFlopNode : Node +{ + #region Properties & Fields + + private bool _lastInput; + private bool _currentValue; + + public InputPin Input { get; } + public OutputPin Output { get; } + + #endregion + + #region Constructors + + public FlipFlopNode() + : base("FlipFlop", "Inverts the output when the input changes from false to true") + { + Input = CreateInputPin(); + Output = CreateOutputPin(); + } + + #endregion + + #region Methods + + /// + public override void Evaluate() + { + bool input = Input.Value; + if (input && !_lastInput) + { + _currentValue = !_currentValue; + Output.Value = _currentValue; + } + + _lastInput = input; + } + + #endregion +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Timing/LatchNode.cs b/src/Artemis.VisualScripting/Nodes/Timing/LatchNode.cs new file mode 100644 index 000000000..a509741b2 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Timing/LatchNode.cs @@ -0,0 +1,113 @@ +using System.Diagnostics; +using Artemis.Core; +using Artemis.Core.Events; +using RGB.NET.Core; + +namespace Artemis.VisualScripting.Nodes.Timing; + +[Node("Latch", "Only passes the input to the output as long as the control-pin is true. If the control pin is false the last passed value is provided.", "Timing", InputType = typeof(object), OutputType = typeof(object))] +public class LatchNode : Node +{ + #region Properties & Fields + + private long _lastUpdateTimestamp = 0; + + public InputPin Control { get; } + public InputPinCollection Input { get; } + + //TODO DarthAffe 21.08.2022: Find something to output to aling in- and outputs + public OutputPin LastUpdateTime { get; } + + private Dictionary _pinPairs = new(); + + #endregion + + #region Constructors + + public LatchNode() + : base("Latch", "Only passes the input to the output as long as the control-pin is true. If the control pin is false the last passed value is provided.") + { + Control = CreateInputPin("Control"); + Input = CreateInputPinCollection(typeof(object), initialCount: 0); + + LastUpdateTime = CreateOutputPin("Last Update"); + + Input.PinAdded += OnInputPinAdded; + Input.PinRemoved += OnInputPinRemoved; + + Input.Add(Input.CreatePin()); + } + + #endregion + + #region Methods + + private void OnInputPinAdded(object? sender, SingleValueEventArgs args) + { + IPin inputPin = args.Value; + _pinPairs.Add(inputPin, CreateOutputPin(typeof(object))); + + inputPin.PinConnected += OnInputPinConnected; + inputPin.PinDisconnected += OnInputPinDisconnected; + + UpdatePinNames(); + } + + private void OnInputPinRemoved(object? sender, SingleValueEventArgs args) + { + IPin inputPin = args.Value; + RemovePin(_pinPairs[inputPin]); + _pinPairs.Remove(inputPin); + + inputPin.PinConnected -= OnInputPinConnected; + inputPin.PinDisconnected -= OnInputPinDisconnected; + + UpdatePinNames(); + } + + private void OnInputPinConnected(object? sender, SingleValueEventArgs args) + { + if (sender is not IPin inputPin || !_pinPairs.ContainsKey(inputPin)) return; + + OutputPin outputPin = _pinPairs[inputPin]; + outputPin.ChangeType(args.Value.Type); + } + + private void OnInputPinDisconnected(object? sender, SingleValueEventArgs args) + { + if (sender is not IPin inputPin || !_pinPairs.ContainsKey(inputPin)) return; + + OutputPin outputPin = _pinPairs[inputPin]; + outputPin.ChangeType(typeof(object)); + } + + private void UpdatePinNames() + { + int counter = 1; + foreach (IPin inputPin in Input.Pins) + { + string name = counter.ToString(); + inputPin.Name = name; + _pinPairs[inputPin].Name = name; + + counter++; + } + } + + /// + public override void Evaluate() + { + if (Control.Value) + { + foreach ((IPin input, OutputPin output) in _pinPairs) + output.Value = input.PinValue; + + LastUpdateTime.Value = 0; + _lastUpdateTimestamp = Stopwatch.GetTimestamp(); + } + else + LastUpdateTime.Value = TimerHelper.GetElapsedTime(_lastUpdateTimestamp); + } + + #endregion +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Timing/SequencerNode.cs b/src/Artemis.VisualScripting/Nodes/Timing/SequencerNode.cs new file mode 100644 index 000000000..6fd2c0ec5 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Timing/SequencerNode.cs @@ -0,0 +1,136 @@ +using Artemis.Core; +using Artemis.Core.Events; + +namespace Artemis.VisualScripting.Nodes.Timing; + +[Node("Sequencer", "Advances on input every time the control has a rising edge (change to true)", "Timing", OutputType = typeof(object))] +public class SequencerNode : Node +{ + #region Properties & Fields + + private int _currentIndex; + private Type _currentType; + private bool _updating; + private IPin? _currentCyclePin; + + private bool _lastInput; + + public InputPin Input { get; } + public InputPinCollection CycleValues { get; } + + public OutputPin Output { get; } + + #endregion + + #region Constructors + + public SequencerNode() + : base("Sequencer", "Advances on input every time the control has a rising edge (change to true)") + { + _currentType = typeof(object); + + Input = CreateInputPin("Control"); + CycleValues = CreateInputPinCollection(typeof(object), "", 0); + Output = CreateOutputPin(typeof(object)); + + CycleValues.PinAdded += CycleValuesOnPinAdded; + CycleValues.PinRemoved += CycleValuesOnPinRemoved; + CycleValues.Add(CycleValues.CreatePin()); + } + + #endregion + + #region Methods + + public override void Evaluate() + { + bool input = Input.Value; + + if (input != _lastInput) + { + _currentIndex++; + + if (_currentIndex >= CycleValues.Count()) + _currentIndex = 0; + + _currentCyclePin = null; + } + + _currentCyclePin ??= CycleValues.ElementAt(_currentIndex); + + object? outputValue = _currentCyclePin.PinValue; + if (Output.Type.IsInstanceOfType(outputValue)) + Output.Value = outputValue; + else if (Output.Type.IsValueType) + Output.Value = Output.Type.GetDefault()!; + + _lastInput = input; + } + + private void CycleValuesOnPinAdded(object? sender, SingleValueEventArgs e) + { + e.Value.PinConnected += OnPinConnected; + e.Value.PinDisconnected += OnPinDisconnected; + + _currentCyclePin = null; + } + + private void CycleValuesOnPinRemoved(object? sender, SingleValueEventArgs e) + { + e.Value.PinConnected -= OnPinConnected; + e.Value.PinDisconnected -= OnPinDisconnected; + + _currentCyclePin = null; + } + + private void OnPinDisconnected(object? sender, SingleValueEventArgs e) => ProcessPinDisconnected(); + + private void OnPinConnected(object? sender, SingleValueEventArgs e) => ProcessPinConnected(e.Value); + + private void ProcessPinConnected(IPin source) + { + if (_updating) + return; + + try + { + _updating = true; + + // No need to change anything if the types haven't changed + if (_currentType != source.Type) + ChangeCurrentType(source.Type); + } + finally + { + _updating = false; + } + } + + private void ChangeCurrentType(Type type) + { + CycleValues.ChangeType(type); + Output.ChangeType(type); + + _currentType = type; + } + + private void ProcessPinDisconnected() + { + if (_updating) + return; + try + { + // If there's still a connected pin, stick to the current type + if (CycleValues.Any(v => v.ConnectedTo.Any())) + return; + + ChangeCurrentType(typeof(object)); + } + finally + { + _updating = false; + } + } + + #endregion +} \ No newline at end of file