From e356d59daabd91c60993bc1266ae3716cb3b1276 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 31 Aug 2021 21:37:31 +0200 Subject: [PATCH] Nodes - Added math expression node Node - Fix visual script cable present event unregister --- .../VisualScripting/Interfaces/IPin.cs | 2 +- src/Artemis.Core/VisualScripting/Pin.cs | 11 +- .../Controls/GradientPicker.xaml.cs | 8 +- src/Artemis.UI/Bootstrapper.cs | 3 + .../Settings/Tabs/About/AboutTabView.xaml | 14 +-- src/Artemis.UI/packages.lock.json | 15 +++ .../Artemis.VisualScripting.csproj | 2 + .../Controls/VisualScriptCablePresenter.cs | 4 +- .../Controls/VisualScriptPinPresenter.cs | 3 + .../Ninject/NoStringNinjectModule.cs | 56 +++++++++ .../MathExpressionNodeCustomViewModel.cs | 37 ++++++ .../MathExpressionNodeCustomView.xaml | 12 ++ .../Nodes/Maths/ExpressionNode.cs | 112 ++++++++++++++++++ .../packages.lock.json | 33 ++++-- 14 files changed, 290 insertions(+), 22 deletions(-) create mode 100644 src/Artemis.VisualScripting/Ninject/NoStringNinjectModule.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Maths/CustomViewModels/MathExpressionNodeCustomViewModel.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Maths/CustomViews/MathExpressionNodeCustomView.xaml create mode 100644 src/Artemis.VisualScripting/Nodes/Maths/ExpressionNode.cs diff --git a/src/Artemis.Core/VisualScripting/Interfaces/IPin.cs b/src/Artemis.Core/VisualScripting/Interfaces/IPin.cs index 8f7f1f93e..4328e440b 100644 --- a/src/Artemis.Core/VisualScripting/Interfaces/IPin.cs +++ b/src/Artemis.Core/VisualScripting/Interfaces/IPin.cs @@ -8,7 +8,7 @@ namespace Artemis.Core { INode Node { get; } - string Name { get; } + string Name { get; set; } PinDirection Direction { get; } Type Type { get; } object PinValue { get; } diff --git a/src/Artemis.Core/VisualScripting/Pin.cs b/src/Artemis.Core/VisualScripting/Pin.cs index 43f5c0e35..eec9734ac 100644 --- a/src/Artemis.Core/VisualScripting/Pin.cs +++ b/src/Artemis.Core/VisualScripting/Pin.cs @@ -10,9 +10,15 @@ namespace Artemis.Core #region Properties & Fields public INode Node { get; } - public string Name { get; } + + public string Name + { + get => _name; + set => SetAndNotify(ref _name, value); + } private bool _isEvaluated; + public bool IsEvaluated { get => _isEvaluated; @@ -20,6 +26,7 @@ namespace Artemis.Core } private readonly List _connectedTo = new(); + private string _name; public IReadOnlyList ConnectedTo => new ReadOnlyCollection(_connectedTo); public abstract PinDirection Direction { get; } @@ -84,4 +91,4 @@ namespace Artemis.Core #endregion } -} +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Controls/GradientPicker.xaml.cs b/src/Artemis.UI.Shared/Controls/GradientPicker.xaml.cs index ad7992140..aaedaa6bc 100644 --- a/src/Artemis.UI.Shared/Controls/GradientPicker.xaml.cs +++ b/src/Artemis.UI.Shared/Controls/GradientPicker.xaml.cs @@ -28,6 +28,7 @@ namespace Artemis.UI.Shared { _gradientConverter = new ColorGradientToGradientStopsConverter(); InitializeComponent(); + Unloaded += OnUnloaded; } /// @@ -121,7 +122,7 @@ namespace Artemis.UI.Shared private void UpdateGradientStops() { - GradientPreview.GradientStops = (GradientStopCollection)_gradientConverter.Convert(ColorGradient, null!, null!, null!); + GradientPreview.GradientStops = (GradientStopCollection) _gradientConverter.Convert(ColorGradient, null!, null!, null!); } private void UIElement_OnMouseUp(object sender, MouseButtonEventArgs e) @@ -137,6 +138,11 @@ namespace Artemis.UI.Shared }); } + private void OnUnloaded(object sender, RoutedEventArgs e) + { + ColorGradient.CollectionChanged -= GradientChanged; + } + /// public event PropertyChangedEventHandler? PropertyChanged; diff --git a/src/Artemis.UI/Bootstrapper.cs b/src/Artemis.UI/Bootstrapper.cs index 8f941ef60..0b44bef81 100644 --- a/src/Artemis.UI/Bootstrapper.cs +++ b/src/Artemis.UI/Bootstrapper.cs @@ -17,6 +17,7 @@ using Artemis.UI.Shared; using Artemis.UI.Shared.Services; using Artemis.UI.Stylet; using Artemis.UI.Utilities; +using Artemis.VisualScripting.Ninject; using Ninject; using Serilog; using Stylet; @@ -127,6 +128,8 @@ namespace Artemis.UI kernel.Load(); // Load the core assembly's module kernel.Load(); + // Load the nodes module + kernel.Load(); base.ConfigureIoC(kernel); } diff --git a/src/Artemis.UI/Screens/Settings/Tabs/About/AboutTabView.xaml b/src/Artemis.UI/Screens/Settings/Tabs/About/AboutTabView.xaml index 95db917e2..8834d071f 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/About/AboutTabView.xaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/About/AboutTabView.xaml @@ -289,13 +289,6 @@ - - Ben.Demystifier - - https://github.com/benaadams/Ben.Demystifier - - - EmbedIO + - NoStringEvaluating + + https://github.com/KovtunV/NoStringEvaluating + + - Ookii.Dialogs.Wpf + + diff --git a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptCablePresenter.cs b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptCablePresenter.cs index d534e4979..f20059d09 100644 --- a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptCablePresenter.cs +++ b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptCablePresenter.cs @@ -88,9 +88,9 @@ namespace Artemis.VisualScripting.Editor.Controls private void OnUnloaded(object sender, RoutedEventArgs e) { if (Cable?.From != null) - Cable.From.PropertyChanged += OnPinPropertyChanged; + Cable.From.PropertyChanged -= OnPinPropertyChanged; if (Cable?.To != null) - Cable.To.PropertyChanged += OnPinPropertyChanged; + Cable.To.PropertyChanged -= OnPinPropertyChanged; } private void OnPathMouseDown(object sender, MouseButtonEventArgs args) diff --git a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPinPresenter.cs b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPinPresenter.cs index 88154da03..f14f4bd04 100644 --- a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPinPresenter.cs +++ b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPinPresenter.cs @@ -71,6 +71,9 @@ namespace Artemis.VisualScripting.Editor.Controls private void OnUnloaded(object sender, RoutedEventArgs e) { LayoutUpdated -= OnLayoutUpdated; + if (Pin != null) + Pin.Node.Node.PropertyChanged -= OnNodePropertyChanged; + _nodePresenter = null; } diff --git a/src/Artemis.VisualScripting/Ninject/NoStringNinjectModule.cs b/src/Artemis.VisualScripting/Ninject/NoStringNinjectModule.cs new file mode 100644 index 000000000..f48a3ac10 --- /dev/null +++ b/src/Artemis.VisualScripting/Ninject/NoStringNinjectModule.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using Microsoft.Extensions.ObjectPool; +using Ninject.Modules; +using NoStringEvaluating; +using NoStringEvaluating.Contract; +using NoStringEvaluating.Models.Values; +using NoStringEvaluating.Services.Cache; +using NoStringEvaluating.Services.Checking; +using NoStringEvaluating.Services.Parsing; +using NoStringEvaluating.Services.Parsing.NodeReaders; + +namespace Artemis.VisualScripting.Ninject +{ + public class NoStringNinjectModule : NinjectModule + { + public override void Load() + { + // Pooling + Bind>>() + .ToConstant(ObjectPool.Create>()) + .InSingletonScope(); + + Bind>>() + .ToConstant(ObjectPool.Create>()) + .InSingletonScope(); + + Bind>() + .ToConstant(ObjectPool.Create()) + .InSingletonScope(); + + // Parser + Bind().To().InSingletonScope(); + Bind().To().InSingletonScope(); + Bind().To().InSingletonScope(); + + // Checker + Bind().To().InSingletonScope(); + + // Evaluator + Bind().To().InSingletonScope(); + + // Options + NoStringEvaluatorOptions opt = new NoStringEvaluatorOptions().SetWordQuotationMark("!"); + opt.UpdateConstants(); + + // If needed + InjectUserDefinedFunctions(); + } + + private void InjectUserDefinedFunctions() + { + IFunctionReader functionReader = (IFunctionReader) Kernel!.GetService(typeof(IFunctionReader)); + NoStringFunctionsInitializer.InitializeFunctions(functionReader, typeof(NoStringNinjectModule)); + } + } +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Maths/CustomViewModels/MathExpressionNodeCustomViewModel.cs b/src/Artemis.VisualScripting/Nodes/Maths/CustomViewModels/MathExpressionNodeCustomViewModel.cs new file mode 100644 index 000000000..fac0f3d54 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Maths/CustomViewModels/MathExpressionNodeCustomViewModel.cs @@ -0,0 +1,37 @@ +using System.ComponentModel; +using Artemis.VisualScripting.Nodes.CustomViewModels; + +namespace Artemis.VisualScripting.Nodes.Maths.CustomViewModels +{ + public class MathExpressionNodeCustomViewModel : CustomNodeViewModel + { + private readonly MathExpressionNode _node; + + public MathExpressionNodeCustomViewModel(MathExpressionNode node) : base(node) + { + _node = node; + } + + public string Input + { + get => (string)_node.Storage; + set => _node.Storage = value; + } + + public override void OnActivate() + { + _node.PropertyChanged += NodeOnPropertyChanged; + } + + public override void OnDeactivate() + { + _node.PropertyChanged -= NodeOnPropertyChanged; + } + + private void NodeOnPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Node.Storage)) + OnPropertyChanged(nameof(Input)); + } + } +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Maths/CustomViews/MathExpressionNodeCustomView.xaml b/src/Artemis.VisualScripting/Nodes/Maths/CustomViews/MathExpressionNodeCustomView.xaml new file mode 100644 index 000000000..0e1d3366f --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Maths/CustomViews/MathExpressionNodeCustomView.xaml @@ -0,0 +1,12 @@ + + + diff --git a/src/Artemis.VisualScripting/Nodes/Maths/ExpressionNode.cs b/src/Artemis.VisualScripting/Nodes/Maths/ExpressionNode.cs new file mode 100644 index 000000000..591ef526f --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Maths/ExpressionNode.cs @@ -0,0 +1,112 @@ +using System; +using System.Linq; +using Artemis.Core; +using Artemis.VisualScripting.Nodes.Maths.CustomViewModels; +using NoStringEvaluating.Contract; +using NoStringEvaluating.Contract.Variables; +using NoStringEvaluating.Models.Values; + +namespace Artemis.VisualScripting.Nodes.Maths +{ + [Node("Math Expression", "Outputs the result of a math expression.", "Math", OutputType = typeof(int))] + public class MathExpressionNode : Node + { + private readonly INoStringEvaluator _evaluator; + private readonly PinsVariablesContainer _variables; + + #region Constructors + public MathExpressionNode(INoStringEvaluator evaluator) + : base("Math Expression", "Outputs the result of a math expression.") + { + _evaluator = evaluator; + Output = CreateOutputPin(); + Values = CreateInputPinCollection("Values", 2); + Values.PinAdded += (_, _) => SetPinNames(); + Values.PinRemoved += (_, _) => SetPinNames(); + _variables = new PinsVariablesContainer(Values); + + SetPinNames(); + } + + #endregion + + #region Properties & Fields + + public OutputPin Output { get; } + public InputPinCollection Values { get; } + + #endregion + + #region Methods + + public override void Evaluate() + { + if (Storage is string formula) + Output.Value = (float) _evaluator.CalcNumber(formula, _variables); + } + + private void SetPinNames() + { + int index = 1; + foreach (IPin value in Values) + { + value.Name = ExcelColumnFromNumber(index).ToLower(); + index++; + } + } + + public static string ExcelColumnFromNumber(int column) + { + string columnString = ""; + decimal columnNumber = column; + while (columnNumber > 0) + { + decimal currentLetterNumber = (columnNumber - 1) % 26; + char currentLetter = (char) (currentLetterNumber + 65); + columnString = currentLetter + columnString; + columnNumber = (columnNumber - (currentLetterNumber + 1)) / 26; + } + + return columnString; + } + + #endregion + } + + public class PinsVariablesContainer : IVariablesContainer + { + private readonly InputPinCollection _values; + + public PinsVariablesContainer(InputPinCollection values) + { + _values = values; + } + + #region Implementation of IVariablesContainer + + /// + public IVariable AddOrUpdate(string name, double value) + { + throw new NotImplementedException(); + } + + /// + public EvaluatorValue GetValue(string name) + { + IPin pin = _values.FirstOrDefault(v => v.Name == name); + return pin == null ? new EvaluatorValue(0) : new EvaluatorValue((double) pin.PinValue); + } + + /// + public bool TryGetValue(string name, out EvaluatorValue value) + { + IPin pin = _values.FirstOrDefault(v => v.Name == name); + double unboxed = (float) pin.PinValue; + value = pin == null ? new EvaluatorValue(0) : new EvaluatorValue(unboxed); + + return pin != null; + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/packages.lock.json b/src/Artemis.VisualScripting/packages.lock.json index c32b192d0..577d20ab1 100644 --- a/src/Artemis.VisualScripting/packages.lock.json +++ b/src/Artemis.VisualScripting/packages.lock.json @@ -8,6 +8,25 @@ "resolved": "2021.1.0", "contentHash": "n9JSw5Z+F+6gp9vSv4aLH6p/bx3GAYA6FZVq1wJq/TJySv/kPgFKLGFeS7A8Xa5X4/GWorh5gd43yjamUgnBNA==" }, + "Ninject": { + "type": "Direct", + "requested": "[3.3.4, )", + "resolved": "3.3.4", + "contentHash": "CmbWW97FfJuh4LEOVZM/spqXl4KAulRUjqeMwRd5J9rDMQArmIYaDMU3pyzXXHT062tbF0OPIMwI7tSOtprPfg==", + "dependencies": { + "System.Reflection.Emit.ILGeneration": "4.3.0", + "System.Reflection.Emit.Lightweight": "4.3.0" + } + }, + "NoStringEvaluating": { + "type": "Direct", + "requested": "[2.2.1, )", + "resolved": "2.2.1", + "contentHash": "dYvWeDdSXrfWreWu17XUNCZD1QAbeMuyOLwVxNBBy+mbs1O/QjsYr1uQPCJWqYRF2Rd5nQBLFbFWybz4Ndh68g==", + "dependencies": { + "Microsoft.Extensions.ObjectPool": "5.0.9" + } + }, "SkiaSharp": { "type": "Direct", "requested": "[2.80.3, )", @@ -107,6 +126,11 @@ "resolved": "5.0.0", "contentHash": "umBECCoMC+sOUgm083yFr8SxTobUOcPFH4AXigdO2xJiszCHAnmeDl4qPphJt+oaJ/XIfV1wOjIts2nRnki61Q==" }, + "Microsoft.Extensions.ObjectPool": { + "type": "Transitive", + "resolved": "5.0.9", + "contentHash": "grj0e6Me0EQsgaurV0fxP0xd8sz8eZVK+Jb816DPzNADHaqXaXJD3xZX9SFjyDl3ykAYvD0y77o5vRd9Hzsk9g==" + }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "5.0.0", @@ -196,15 +220,6 @@ "resolved": "13.0.1", "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" }, - "Ninject": { - "type": "Transitive", - "resolved": "3.3.4", - "contentHash": "CmbWW97FfJuh4LEOVZM/spqXl4KAulRUjqeMwRd5J9rDMQArmIYaDMU3pyzXXHT062tbF0OPIMwI7tSOtprPfg==", - "dependencies": { - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Emit.Lightweight": "4.3.0" - } - }, "Ninject.Extensions.ChildKernel": { "type": "Transitive", "resolved": "3.3.0",