From d6372dffad36a51097e0c7e45a96efc7fa9a5423 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 16 Sep 2021 21:11:23 +0200 Subject: [PATCH] Nodes - Added enum equality node Conditions - Implemented event trigger conditions --- .../Profile/Conditions/EventCondition.cs | 48 ++++-- .../VisualScripting/NodeScript.cs | 12 ++ src/Artemis.UI.Shared/packages.lock.json | 2 +- .../DisplayConditionsView.xaml | 2 +- .../Event/EventConditionView.xaml | 158 +++++++++++------- .../Event/EventConditionViewModel.cs | 43 ++++- .../Static/StaticConditionView.xaml | 15 +- .../Services/RegistrationService.cs | 2 +- src/Artemis.UI/packages.lock.json | 2 +- .../Controls/VisualScriptNodeCreationBox.cs | 4 +- .../Controls/VisualScriptPinPresenter.cs | 1 + .../Editor/Controls/VisualScriptPresenter.cs | 2 +- .../EnumEqualsNodeCustomViewModel.cs | 65 +++++++ .../CustomViews/EnumEqualsNodeCustomView.xaml | 26 +++ .../Nodes/EnumEqualsNode.cs | 30 ++++ .../packages.lock.json | 2 +- 16 files changed, 321 insertions(+), 93 deletions(-) create mode 100644 src/Artemis.VisualScripting/Nodes/CustomViewModels/EnumEqualsNodeCustomViewModel.cs create mode 100644 src/Artemis.VisualScripting/Nodes/CustomViews/EnumEqualsNodeCustomView.xaml create mode 100644 src/Artemis.VisualScripting/Nodes/EnumEqualsNode.cs diff --git a/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs b/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs index 3923733fe..0f787c91b 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs @@ -10,18 +10,21 @@ namespace Artemis.Core { private readonly string _displayName; private readonly EventConditionEntity _entity; - private EventDefaultNode _eventNode; + private EventDefaultNode? _eventNode; private TimeLineEventOverlapMode _eventOverlapMode; private DataModelPath? _eventPath; private DateTime _lastProcessedTrigger; + private NodeScript? _script; + /// + /// Creates a new instance of the class + /// public EventCondition(ProfileElement profileElement) { _entity = new EventConditionEntity(); _displayName = profileElement.GetType().Name; ProfileElement = profileElement; - Script = new NodeScript($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", profileElement.Profile); } internal EventCondition(EventConditionEntity entity, ProfileElement profileElement) @@ -36,18 +39,21 @@ namespace Artemis.Core /// /// Gets the script that drives the event condition /// - public NodeScript Script { get; private set; } + public NodeScript? Script + { + get => _script; + set => SetAndNotify(ref _script, value); + } /// /// Gets or sets the path to the event that drives this event condition /// public DataModelPath? EventPath { - set => SetAndNotify(ref _eventPath, value); get => _eventPath; + set => SetAndNotify(ref _eventPath, value); } - /// /// Gets or sets how the condition behaves when events trigger before the timeline finishes /// @@ -62,7 +68,7 @@ namespace Artemis.Core /// public void UpdateEventNode() { - if (EventPath?.GetValue() is not IDataModelEvent dataModelEvent) + if (Script == null || EventPath?.GetValue() is not IDataModelEvent dataModelEvent) return; if (Script.Nodes.FirstOrDefault(n => n is EventDefaultNode) is EventDefaultNode existing) @@ -72,7 +78,7 @@ namespace Artemis.Core } else { - _eventNode = new EventDefaultNode(); + _eventNode = new EventDefaultNode() {X = -300}; _eventNode.UpdateDataModelEvent(dataModelEvent); } @@ -82,13 +88,24 @@ namespace Artemis.Core Script.RemoveNode(_eventNode); } + /// + /// Updates the with a new empty node script + /// + public void CreateEmptyNodeScript() + { + Script?.Dispose(); + Script = new NodeScript($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", ProfileElement.Profile); + UpdateEventNode(); + } + private bool Evaluate() { if (EventPath?.GetValue() is not IDataModelEvent dataModelEvent || dataModelEvent.LastTrigger <= _lastProcessedTrigger) return false; _lastProcessedTrigger = dataModelEvent.LastTrigger; - if (!Script.ExitNodeConnected) + + if (Script == null) return true; Script.Run(); @@ -149,7 +166,7 @@ namespace Artemis.Core /// public void Dispose() { - Script.Dispose(); + Script?.Dispose(); EventPath?.Dispose(); } @@ -159,16 +176,18 @@ namespace Artemis.Core public void Load() { EventOverlapMode = (TimeLineEventOverlapMode) _entity.EventOverlapMode; - Script = new NodeScript($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", _entity.Script, ProfileElement.Profile); - EventPath = new DataModelPath(_entity.EventPath); + if (_entity.Script != null) + Script = new NodeScript($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", _entity.Script, ProfileElement.Profile); + if (_entity.EventPath != null) + EventPath = new DataModelPath(_entity.EventPath); } /// public void Save() { _entity.EventOverlapMode = (int) EventOverlapMode; - Script.Save(); - _entity.Script = Script.Entity; + Script?.Save(); + _entity.Script = Script?.Entity; EventPath?.Save(); _entity.EventPath = EventPath?.Entity; } @@ -176,6 +195,9 @@ namespace Artemis.Core /// public void LoadNodeScript() { + if (Script == null) + return; + Script.Load(); UpdateEventNode(); Script.LoadConnections(); diff --git a/src/Artemis.Core/VisualScripting/NodeScript.cs b/src/Artemis.Core/VisualScripting/NodeScript.cs index 8f5263fa6..8458136d0 100644 --- a/src/Artemis.Core/VisualScripting/NodeScript.cs +++ b/src/Artemis.Core/VisualScripting/NodeScript.cs @@ -189,6 +189,18 @@ namespace Artemis.Core if (sourcePin.Direction == targetPin.Direction) continue; + // Clear existing connections on input pins, we don't want none of that now + if (targetPin.Direction == PinDirection.Input) + { + while (targetPin.ConnectedTo.Any()) + targetPin.DisconnectFrom(targetPin.ConnectedTo[0]); + } + if (sourcePin.Direction == PinDirection.Input) + { + while (sourcePin.ConnectedTo.Any()) + sourcePin.DisconnectFrom(sourcePin.ConnectedTo[0]); + } + // Only connect the nodes if they aren't already connected (LoadConnections may be called twice or more) if (!targetPin.ConnectedTo.Contains(sourcePin)) targetPin.ConnectTo(sourcePin); diff --git a/src/Artemis.UI.Shared/packages.lock.json b/src/Artemis.UI.Shared/packages.lock.json index 3ed7085b2..23ece4496 100644 --- a/src/Artemis.UI.Shared/packages.lock.json +++ b/src/Artemis.UI.Shared/packages.lock.json @@ -1,7 +1,7 @@ { "version": 1, "dependencies": { - ".NETCoreApp,Version=v5.0": { + "net5.0-windows7.0": { "Humanizer.Core": { "type": "Direct", "requested": "[2.11.10, )", diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml index acf304468..beb277709 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml @@ -52,7 +52,7 @@ - + NONE diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Event/EventConditionView.xaml b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Event/EventConditionView.xaml index b27aef128..ca4f75c29 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Event/EventConditionView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Event/EventConditionView.xaml @@ -1,19 +1,21 @@ - + + @@ -24,21 +26,39 @@ + + + + - + - + + + @@ -66,19 +86,29 @@ - - - - Click to edit script - - - - - Scripts with nothing connected to their end-node are always true. - - + + + Click to edit script + + + + + + + + Conditional trigger disabled + + + + When enabled, the layer will only trigger if the script evaluates to true + + + @@ -86,7 +116,7 @@ - Configure how the layer should act when the event(s) trigger + @@ -102,63 +132,71 @@ - - - RESTART - + IsChecked="{Binding EventOverlapMode, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Restart}, Converter={StaticResource ComparisonConverter}}"> - Stop the current run and restart the timeline + + + + + + + - - - TOGGLE - + IsChecked="{Binding EventOverlapMode, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Toggle}, Converter={StaticResource ComparisonConverter}}"> - Repeat the timeline until the event fires again + + + + + + + - - - IGNORE - + IsChecked="{Binding EventOverlapMode, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Ignore}, Converter={StaticResource ComparisonConverter}}"> - Ignore subsequent event fires until the timeline finishes + + + + + + + - - - COPY - + IsChecked="{Binding EventOverlapMode, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Copy}, Converter={StaticResource ComparisonConverter}}"> - Play another copy of the timeline on top of the current run + + + + + + + diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Event/EventConditionViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Event/EventConditionViewModel.cs index 09f790cb0..ec3219de3 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Event/EventConditionViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Event/EventConditionViewModel.cs @@ -12,9 +12,10 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions.Event { public class EventConditionViewModel : Screen { + private readonly INodeVmFactory _nodeVmFactory; private readonly IProfileEditorService _profileEditorService; private readonly IWindowManager _windowManager; - private readonly INodeVmFactory _nodeVmFactory; + private NodeScript _oldScript; public EventConditionViewModel(EventCondition eventCondition, IProfileEditorService profileEditorService, IWindowManager windowManager, INodeVmFactory nodeVmFactory) { @@ -22,6 +23,10 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions.Event _windowManager = windowManager; _nodeVmFactory = nodeVmFactory; EventCondition = eventCondition; + + FilterTypes = new BindableCollection {typeof(IDataModelEvent)}; + if (_profileEditorService.SelectedProfileConfiguration?.Module != null) + Modules = new BindableCollection {_profileEditorService.SelectedProfileConfiguration.Module}; } public EventCondition EventCondition { get; } @@ -39,6 +44,42 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions.Event } } + public bool TriggerConditionally + { + get => EventCondition.Script != null; + set + { + if (EventCondition.Script != null) + { + _oldScript = EventCondition.Script; + EventCondition.Script = null; + } + else + { + if (_oldScript != null) + { + EventCondition.Script = _oldScript; + _oldScript = null; + } + else + { + EventCondition.CreateEmptyNodeScript(); + } + } + } + } + + #region Overrides of Screen + + /// + protected override void OnClose() + { + _oldScript?.Dispose(); + base.OnClose(); + } + + #endregion + public void DataModelPathSelected(object sender, DataModelSelectedEventArgs e) { EventCondition.UpdateEventNode(); diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Static/StaticConditionView.xaml b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Static/StaticConditionView.xaml index 21a143354..cd33b385c 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Static/StaticConditionView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Static/StaticConditionView.xaml @@ -49,17 +49,10 @@ - - - - Click to edit script - - - - - Scripts with nothing connected to their end-node are always true. - - + + Click to edit script + + diff --git a/src/Artemis.UI/Services/RegistrationService.cs b/src/Artemis.UI/Services/RegistrationService.cs index 2d90b2a02..41c1ad47c 100644 --- a/src/Artemis.UI/Services/RegistrationService.cs +++ b/src/Artemis.UI/Services/RegistrationService.cs @@ -124,10 +124,10 @@ namespace Artemis.UI.Services _nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(bool), new SKColor(0xFFCD3232)); _nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(string), new SKColor(0xFFFFD700)); _nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(int), new SKColor(0xFF32CD32)); - _nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(double), new SKColor(0xFF1E90FF)); _nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(float), new SKColor(0xFFFF7C00)); _nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(SKColor), new SKColor(0xFFAD3EED)); _nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(IList), new SKColor(0xFFED3E61)); + _nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(Enum), new SKColor(0xFF1E90FF)); foreach (Type nodeType in typeof(SumIntegersNode).Assembly.GetTypes().Where(t => typeof(INode).IsAssignableFrom(t) && t.IsPublic && !t.IsAbstract && !t.IsInterface)) _nodeService.RegisterNodeType(Constants.CorePlugin, nodeType); diff --git a/src/Artemis.UI/packages.lock.json b/src/Artemis.UI/packages.lock.json index 53f59c83c..8c353ad06 100644 --- a/src/Artemis.UI/packages.lock.json +++ b/src/Artemis.UI/packages.lock.json @@ -1,7 +1,7 @@ { "version": 1, "dependencies": { - ".NETCoreApp,Version=v5.0": { + "net5.0-windows10.0.17763": { "FluentValidation": { "type": "Direct", "requested": "[10.3.0, )", diff --git a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptNodeCreationBox.cs b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptNodeCreationBox.cs index cd9950722..3a266d336 100644 --- a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptNodeCreationBox.cs +++ b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptNodeCreationBox.cs @@ -114,8 +114,8 @@ namespace Artemis.VisualScripting.Editor.Controls return nameContains; if (SourcePin.Pin.Direction == PinDirection.Input) - return nameContains && (nodeData.OutputType == typeof(object) || nodeData.OutputType == SourcePin.Pin.Type); - return nameContains && (nodeData.InputType == typeof(object) || nodeData.InputType == SourcePin.Pin.Type); + return nodeData.OutputType != null && nameContains && (nodeData.OutputType == typeof(object) || nodeData.OutputType.IsAssignableTo(SourcePin.Pin.Type)); + return nodeData.InputType != null && nameContains && (nodeData.InputType == typeof(object) || nodeData.InputType.IsAssignableFrom(SourcePin.Pin.Type)); } private void ItemsSourceChanged() diff --git a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPinPresenter.cs b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPinPresenter.cs index f14f4bd04..a823cdac6 100644 --- a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPinPresenter.cs +++ b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPinPresenter.cs @@ -190,6 +190,7 @@ namespace Artemis.VisualScripting.Editor.Controls } private bool IsTypeCompatible(Type type) => (Pin.Pin.Type == type) + || (Pin.Pin.Type == typeof(Enum) && type.IsEnum) || ((Pin.Pin.Direction == PinDirection.Input) && (Pin.Pin.Type == typeof(object))) || ((Pin.Pin.Direction == PinDirection.Output) && (type == typeof(object))); diff --git a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPresenter.cs b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPresenter.cs index 3860f1ebb..e2573ed4e 100644 --- a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPresenter.cs +++ b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPresenter.cs @@ -537,7 +537,7 @@ namespace Artemis.VisualScripting.Editor.Controls // Connect to the first matching input or output pin List pins = node.Pins.ToList(); pins.AddRange(node.PinCollections.SelectMany(c => c)); - pins = pins.Where(p => p.Type == typeof(object) || p.Type == SourcePin.Pin.Type).OrderBy(p => p.Type != typeof(object)).ToList(); + pins = pins.Where(p => p.Type == typeof(object) || p.Type.IsAssignableFrom(SourcePin.Pin.Type)).OrderBy(p => p.Type != typeof(object)).ToList(); IPin preferredPin = SourcePin.Pin.Direction == PinDirection.Input ? pins.FirstOrDefault(p => p.Direction == PinDirection.Output) diff --git a/src/Artemis.VisualScripting/Nodes/CustomViewModels/EnumEqualsNodeCustomViewModel.cs b/src/Artemis.VisualScripting/Nodes/CustomViewModels/EnumEqualsNodeCustomViewModel.cs new file mode 100644 index 000000000..d03fa5395 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/CustomViewModels/EnumEqualsNodeCustomViewModel.cs @@ -0,0 +1,65 @@ +using System; +using System.ComponentModel; +using Artemis.Core; +using Artemis.Core.Events; +using Artemis.UI.Shared; +using Stylet; + +namespace Artemis.VisualScripting.Nodes.CustomViewModels +{ + public class EnumEqualsNodeCustomViewModel : CustomNodeViewModel + { + private readonly EnumEqualsNode _node; + + public EnumEqualsNodeCustomViewModel(EnumEqualsNode node) : base(node) + { + _node = node; + } + + public Enum Input + { + get => _node.Storage as Enum; + set => _node.Storage = value; + } + + public BindableCollection EnumValues { get; } = new(); + + public override void OnActivate() + { + _node.InputPin.PinConnected += InputPinOnPinConnected; + _node.InputPin.PinDisconnected += InputPinOnPinDisconnected; + _node.PropertyChanged += NodeOnPropertyChanged; + + if (_node.InputPin.Value != null && _node.InputPin.Value.GetType().IsEnum) + EnumValues.AddRange(EnumUtilities.GetAllValuesAndDescriptions(_node.InputPin.Value.GetType())); + base.OnActivate(); + } + + public override void OnDeactivate() + { + _node.InputPin.PinConnected -= InputPinOnPinConnected; + _node.InputPin.PinDisconnected -= InputPinOnPinDisconnected; + _node.PropertyChanged -= NodeOnPropertyChanged; + + base.OnDeactivate(); + } + + private void InputPinOnPinDisconnected(object sender, SingleValueEventArgs e) + { + EnumValues.Clear(); + } + + private void InputPinOnPinConnected(object sender, SingleValueEventArgs e) + { + EnumValues.Clear(); + if (_node.InputPin.Value != null && _node.InputPin.Value.GetType().IsEnum) + EnumValues.AddRange(EnumUtilities.GetAllValuesAndDescriptions(_node.InputPin.Value.GetType())); + } + + 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/CustomViews/EnumEqualsNodeCustomView.xaml b/src/Artemis.VisualScripting/Nodes/CustomViews/EnumEqualsNodeCustomView.xaml new file mode 100644 index 000000000..01c753dcf --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/CustomViews/EnumEqualsNodeCustomView.xaml @@ -0,0 +1,26 @@ + + + + + + + + + + + diff --git a/src/Artemis.VisualScripting/Nodes/EnumEqualsNode.cs b/src/Artemis.VisualScripting/Nodes/EnumEqualsNode.cs new file mode 100644 index 000000000..59df2269e --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/EnumEqualsNode.cs @@ -0,0 +1,30 @@ +using System; +using Artemis.Core; +using Artemis.Core.Events; +using Artemis.VisualScripting.Nodes.CustomViewModels; + +namespace Artemis.VisualScripting.Nodes +{ + [Node("Enum Equals", "Determines the equality between an input and a selected enum value", InputType = typeof(Enum), OutputType = typeof(bool))] + public class EnumEqualsNode : Node + { + public EnumEqualsNode() : base("Enum Equals", "Determines the equality between an input and a selected enum value") + { + InputPin = CreateInputPin(); + OutputPin = CreateOutputPin(); + } + + public InputPin InputPin { get; } + public OutputPin OutputPin { get; } + + #region Overrides of Node + + /// + public override void Evaluate() + { + OutputPin.Value = InputPin.Value != null && InputPin.Value.Equals(Storage); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/packages.lock.json b/src/Artemis.VisualScripting/packages.lock.json index 8f10fb4ab..c63f7f03a 100644 --- a/src/Artemis.VisualScripting/packages.lock.json +++ b/src/Artemis.VisualScripting/packages.lock.json @@ -1,7 +1,7 @@ { "version": 1, "dependencies": { - ".NETCoreApp,Version=v5.0": { + "net5.0-windows7.0": { "JetBrains.Annotations": { "type": "Direct", "requested": "[2021.1.0, )",