From a45dd094a08e7ee09d07d5e928fc756daae046c3 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Thu, 15 Jul 2021 00:20:08 +0200 Subject: [PATCH 01/47] Added first visual-scripting draft [WIP] --- src/Artemis.Core/VisualScripting/INode.cs | 26 + src/Artemis.Core/VisualScripting/IPin.cs | 20 + .../VisualScripting/IPinCollection.cs | 16 + src/Artemis.Core/VisualScripting/IScript.cs | 25 + .../VisualScripting/PinDirection.cs | 8 + src/Artemis.UI/App.xaml | 7 +- src/Artemis.UI/Artemis.UI.csproj | 1 + .../DisplayConditionsView.xaml | 7 +- .../DisplayConditionsViewModel.cs | 66 +- src/Artemis.UI/packages.lock.json | 12 + .../Artemis.VisualScripting.csproj | 52 + .../Attributes/UIAttribute.cs | 37 + .../Controls/VisualScriptCablePresenter.cs | 68 + .../Editor/Controls/VisualScriptEditor.cs | 41 + .../Controls/VisualScriptNodeCreationBox.cs | 115 ++ .../Controls/VisualScriptNodePresenter.cs | 137 ++ .../Controls/VisualScriptPinPresenter.cs | 184 +++ .../Editor/Controls/VisualScriptPresenter.cs | 437 ++++++ .../Editor/Controls/Wrapper/VisualScript.cs | 189 +++ .../Controls/Wrapper/VisualScriptCable.cs | 67 + .../Controls/Wrapper/VisualScriptNode.cs | 179 +++ .../Controls/Wrapper/VisualScriptPin.cs | 112 ++ .../Wrapper/VisualScriptPinCollection.cs | 58 + .../Editor/EditorStyles.xaml | 13 + .../Styles/VisualScriptCablePresenter.xaml | 68 + .../Editor/Styles/VisualScriptEditor.xaml | 28 + .../Styles/VisualScriptNodeCreationBox.xaml | 54 + .../Styles/VisualScriptNodePresenter.xaml | 262 ++++ .../Styles/VisualScriptPinPresenter.xaml | 113 ++ .../Editor/Styles/VisualScriptPresenter.xaml | 111 ++ .../Events/PinConnectedEventArgs.cs | 25 + .../Events/PinDisconnectedEventArgs.cs | 25 + .../VisualScriptNodeDragMovingEventArgs.cs | 24 + ...ualScriptNodeIsSelectedChangedEventArgs.cs | 24 + .../Internal/ExitNode.cs | 40 + .../Internal/IsConnectingPin.cs | 26 + src/Artemis.VisualScripting/Model/InputPin.cs | 51 + .../Model/InputPinCollection.cs | 35 + src/Artemis.VisualScripting/Model/Node.cs | 117 ++ src/Artemis.VisualScripting/Model/NodeData.cs | 38 + .../Model/OutputPin.cs | 41 + .../Model/OutputPinCollection.cs | 29 + src/Artemis.VisualScripting/Model/Pin.cs | 64 + .../Model/PinCollection.cs | 55 + src/Artemis.VisualScripting/Model/Script.cs | 91 ++ .../Nodes/BoolOperations.cs | 160 +++ .../Nodes/ConvertNodes.cs | 107 ++ .../Nodes/StaticValueNodes.cs | 121 ++ .../Nodes/StringFormatNode.cs | 40 + .../Nodes/Styles/StaticValueNodes.xaml | 12 + src/Artemis.VisualScripting/Nodes/SumNode.cs | 70 + .../Services/NodeService.cs | 59 + .../ViewModel/AbstractBindable.cs | 64 + .../ViewModel/ActionCommand.cs | 77 + .../packages.lock.json | 1246 +++++++++++++++++ src/Artemis.sln | 6 + 56 files changed, 5139 insertions(+), 21 deletions(-) create mode 100644 src/Artemis.Core/VisualScripting/INode.cs create mode 100644 src/Artemis.Core/VisualScripting/IPin.cs create mode 100644 src/Artemis.Core/VisualScripting/IPinCollection.cs create mode 100644 src/Artemis.Core/VisualScripting/IScript.cs create mode 100644 src/Artemis.Core/VisualScripting/PinDirection.cs create mode 100644 src/Artemis.VisualScripting/Artemis.VisualScripting.csproj create mode 100644 src/Artemis.VisualScripting/Attributes/UIAttribute.cs create mode 100644 src/Artemis.VisualScripting/Editor/Controls/VisualScriptCablePresenter.cs create mode 100644 src/Artemis.VisualScripting/Editor/Controls/VisualScriptEditor.cs create mode 100644 src/Artemis.VisualScripting/Editor/Controls/VisualScriptNodeCreationBox.cs create mode 100644 src/Artemis.VisualScripting/Editor/Controls/VisualScriptNodePresenter.cs create mode 100644 src/Artemis.VisualScripting/Editor/Controls/VisualScriptPinPresenter.cs create mode 100644 src/Artemis.VisualScripting/Editor/Controls/VisualScriptPresenter.cs create mode 100644 src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScript.cs create mode 100644 src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptCable.cs create mode 100644 src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptNode.cs create mode 100644 src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptPin.cs create mode 100644 src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptPinCollection.cs create mode 100644 src/Artemis.VisualScripting/Editor/EditorStyles.xaml create mode 100644 src/Artemis.VisualScripting/Editor/Styles/VisualScriptCablePresenter.xaml create mode 100644 src/Artemis.VisualScripting/Editor/Styles/VisualScriptEditor.xaml create mode 100644 src/Artemis.VisualScripting/Editor/Styles/VisualScriptNodeCreationBox.xaml create mode 100644 src/Artemis.VisualScripting/Editor/Styles/VisualScriptNodePresenter.xaml create mode 100644 src/Artemis.VisualScripting/Editor/Styles/VisualScriptPinPresenter.xaml create mode 100644 src/Artemis.VisualScripting/Editor/Styles/VisualScriptPresenter.xaml create mode 100644 src/Artemis.VisualScripting/Events/PinConnectedEventArgs.cs create mode 100644 src/Artemis.VisualScripting/Events/PinDisconnectedEventArgs.cs create mode 100644 src/Artemis.VisualScripting/Events/VisualScriptNodeDragMovingEventArgs.cs create mode 100644 src/Artemis.VisualScripting/Events/VisualScriptNodeIsSelectedChangedEventArgs.cs create mode 100644 src/Artemis.VisualScripting/Internal/ExitNode.cs create mode 100644 src/Artemis.VisualScripting/Internal/IsConnectingPin.cs create mode 100644 src/Artemis.VisualScripting/Model/InputPin.cs create mode 100644 src/Artemis.VisualScripting/Model/InputPinCollection.cs create mode 100644 src/Artemis.VisualScripting/Model/Node.cs create mode 100644 src/Artemis.VisualScripting/Model/NodeData.cs create mode 100644 src/Artemis.VisualScripting/Model/OutputPin.cs create mode 100644 src/Artemis.VisualScripting/Model/OutputPinCollection.cs create mode 100644 src/Artemis.VisualScripting/Model/Pin.cs create mode 100644 src/Artemis.VisualScripting/Model/PinCollection.cs create mode 100644 src/Artemis.VisualScripting/Model/Script.cs create mode 100644 src/Artemis.VisualScripting/Nodes/BoolOperations.cs create mode 100644 src/Artemis.VisualScripting/Nodes/ConvertNodes.cs create mode 100644 src/Artemis.VisualScripting/Nodes/StaticValueNodes.cs create mode 100644 src/Artemis.VisualScripting/Nodes/StringFormatNode.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Styles/StaticValueNodes.xaml create mode 100644 src/Artemis.VisualScripting/Nodes/SumNode.cs create mode 100644 src/Artemis.VisualScripting/Services/NodeService.cs create mode 100644 src/Artemis.VisualScripting/ViewModel/AbstractBindable.cs create mode 100644 src/Artemis.VisualScripting/ViewModel/ActionCommand.cs create mode 100644 src/Artemis.VisualScripting/packages.lock.json diff --git a/src/Artemis.Core/VisualScripting/INode.cs b/src/Artemis.Core/VisualScripting/INode.cs new file mode 100644 index 000000000..d304f179e --- /dev/null +++ b/src/Artemis.Core/VisualScripting/INode.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; + +namespace Artemis.Core.VisualScripting +{ + public interface INode : INotifyPropertyChanged + { + string Name { get; } + string Description { get; } + + public double X { get; set; } + public double Y { get; set; } + + public IReadOnlyCollection Pins { get; } + public IReadOnlyCollection PinCollections { get; } + + public object CustomView { get; } + public object CustomViewModel { get; } + + event EventHandler Resetting; + + void Evaluate(); + void Reset(); + } +} diff --git a/src/Artemis.Core/VisualScripting/IPin.cs b/src/Artemis.Core/VisualScripting/IPin.cs new file mode 100644 index 000000000..9b2cb9245 --- /dev/null +++ b/src/Artemis.Core/VisualScripting/IPin.cs @@ -0,0 +1,20 @@ +using System; +using Artemis.VisualScripting.Model; + +namespace Artemis.Core.VisualScripting +{ + public interface IPin + { + INode Node { get; } + + string Name { get; } + PinDirection Direction { get; } + Type Type { get; } + object PinValue { get; } + + bool IsEvaluated { get; set; } + + void ConnectTo(IPin pin); + void DisconnectFrom(IPin pin); + } +} diff --git a/src/Artemis.Core/VisualScripting/IPinCollection.cs b/src/Artemis.Core/VisualScripting/IPinCollection.cs new file mode 100644 index 000000000..d9c2a1b33 --- /dev/null +++ b/src/Artemis.Core/VisualScripting/IPinCollection.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using Artemis.VisualScripting.Model; + +namespace Artemis.Core.VisualScripting +{ + public interface IPinCollection : IEnumerable + { + string Name { get; } + PinDirection Direction { get; } + Type Type { get; } + + IPin AddPin(); + bool Remove(IPin pin); + } +} diff --git a/src/Artemis.Core/VisualScripting/IScript.cs b/src/Artemis.Core/VisualScripting/IScript.cs new file mode 100644 index 000000000..abcc13516 --- /dev/null +++ b/src/Artemis.Core/VisualScripting/IScript.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; + +namespace Artemis.Core.VisualScripting +{ + public interface IScript : INotifyPropertyChanged, IDisposable + { + string Name { get; } + string Description { get; } + + IEnumerable Nodes { get; } + + Type ResultType { get; } + + void Run(); + void AddNode(INode node); + void RemoveNode(INode node); + } + + public interface IScript : IScript + { + T Result { get; } + } +} diff --git a/src/Artemis.Core/VisualScripting/PinDirection.cs b/src/Artemis.Core/VisualScripting/PinDirection.cs new file mode 100644 index 000000000..cc4b8500b --- /dev/null +++ b/src/Artemis.Core/VisualScripting/PinDirection.cs @@ -0,0 +1,8 @@ +namespace Artemis.VisualScripting.Model +{ + public enum PinDirection + { + Input, + Output + } +} diff --git a/src/Artemis.UI/App.xaml b/src/Artemis.UI/App.xaml index 1e96c39a4..f23a62a35 100644 --- a/src/Artemis.UI/App.xaml +++ b/src/Artemis.UI/App.xaml @@ -34,16 +34,19 @@ - + + + + - + pack://application:,,,/Resources/Fonts/#Roboto Mono diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index 372979bc4..c7a744e8a 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -82,6 +82,7 @@ true + diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml index be801816d..0261dc31c 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml @@ -8,6 +8,7 @@ xmlns:converters="clr-namespace:Artemis.UI.Converters" xmlns:displayConditions="clr-namespace:Artemis.UI.Screens.ProfileEditor.DisplayConditions" xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core" + xmlns:controls="clr-namespace:Artemis.VisualScripting.Editor.Controls;assembly=Artemis.VisualScripting" x:Class="Artemis.UI.Screens.ProfileEditor.DisplayConditions.DisplayConditionsView" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" @@ -40,7 +41,9 @@ - + + + diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs index fad56c3dc..472c38dc1 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs @@ -1,13 +1,13 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using System.Linq; using Artemis.Core; -using Artemis.Core.Modules; using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.ProfileEditor.Conditions; using Artemis.UI.Shared; using Artemis.UI.Shared.Services; +using Artemis.VisualScripting.Model; +using Artemis.VisualScripting.Services; using Stylet; namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions @@ -24,6 +24,35 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions { _profileEditorService = profileEditorService; _dataModelConditionsVmFactory = dataModelConditionsVmFactory; + + AvailableNodes = _nodeService.AvailableNodes; + Script = new Script("Display Condition (TODO)", "TODO"); + } + + #region TODO + + private static NodeService _nodeService; + + static DisplayConditionsViewModel() + { + _nodeService = new NodeService(); + _nodeService.InitializeNodes(); + } + + #endregion + + private Script _script; + public Script Script + { + get => _script; + private set => SetAndNotify(ref _script, value); + } + + private IEnumerable _availableNodes; + public IEnumerable AvailableNodes + { + get => _availableNodes; + set => SetAndNotify(ref _availableNodes, value); } public bool DisplayStartHint @@ -110,8 +139,8 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions { if (RenderProfileElement != null) { - RenderProfileElement.DisplayCondition.ChildAdded -= DisplayConditionOnChildrenModified; - RenderProfileElement.DisplayCondition.ChildRemoved -= DisplayConditionOnChildrenModified; + //RenderProfileElement.DisplayCondition.ChildAdded -= DisplayConditionOnChildrenModified; + //RenderProfileElement.DisplayCondition.ChildRemoved -= DisplayConditionOnChildrenModified; RenderProfileElement.Timeline.PropertyChanged -= TimelineOnPropertyChanged; } @@ -128,23 +157,26 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions } // Ensure the layer has a root display condition group + //if (renderProfileElement.DisplayCondition == null) + // renderProfileElement.DisplayCondition = new DataModelConditionGroup(null); + if (renderProfileElement.DisplayCondition == null) - renderProfileElement.DisplayCondition = new DataModelConditionGroup(null); + renderProfileElement.DisplayCondition = new Script("Display Condition (TODO)", "-"); - List modules = new(); - if (_profileEditorService.SelectedProfileConfiguration?.Module != null) - modules.Add(_profileEditorService.SelectedProfileConfiguration.Module); - ActiveItem = _dataModelConditionsVmFactory.DataModelConditionGroupViewModel(renderProfileElement.DisplayCondition, ConditionGroupType.General, modules); - ActiveItem.IsRootGroup = true; + //List modules = new(); + //if (_profileEditorService.SelectedProfileConfiguration?.Module != null) + // modules.Add(_profileEditorService.SelectedProfileConfiguration.Module); + //ActiveItem = _dataModelConditionsVmFactory.DataModelConditionGroupViewModel(renderProfileElement.DisplayCondition, ConditionGroupType.General, modules); + //ActiveItem.IsRootGroup = true; - DisplayStartHint = !RenderProfileElement.DisplayCondition.Children.Any(); - IsEventCondition = RenderProfileElement.DisplayCondition.Children.Any(c => c is DataModelConditionEvent); + //DisplayStartHint = !RenderProfileElement.DisplayCondition.Children.Any(); + //IsEventCondition = RenderProfileElement.DisplayCondition.Children.Any(c => c is DataModelConditionEvent); - RenderProfileElement.DisplayCondition.ChildAdded += DisplayConditionOnChildrenModified; - RenderProfileElement.DisplayCondition.ChildRemoved += DisplayConditionOnChildrenModified; + //RenderProfileElement.DisplayCondition.ChildAdded += DisplayConditionOnChildrenModified; + //RenderProfileElement.DisplayCondition.ChildRemoved += DisplayConditionOnChildrenModified; RenderProfileElement.Timeline.PropertyChanged += TimelineOnPropertyChanged; } - + private void TimelineOnPropertyChanged(object sender, PropertyChangedEventArgs e) { NotifyOfPropertyChange(nameof(DisplayContinuously)); @@ -154,8 +186,8 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions private void DisplayConditionOnChildrenModified(object sender, EventArgs e) { - DisplayStartHint = !RenderProfileElement.DisplayCondition.Children.Any(); - IsEventCondition = RenderProfileElement.DisplayCondition.Children.Any(c => c is DataModelConditionEvent); + //DisplayStartHint = !RenderProfileElement.DisplayCondition.Children.Any(); + //IsEventCondition = RenderProfileElement.DisplayCondition.Children.Any(c => c is DataModelConditionEvent); } public void EventTriggerModeSelected() diff --git a/src/Artemis.UI/packages.lock.json b/src/Artemis.UI/packages.lock.json index ef2b15f3e..88c76e6f1 100644 --- a/src/Artemis.UI/packages.lock.json +++ b/src/Artemis.UI/packages.lock.json @@ -247,6 +247,11 @@ "resolved": "2.1.0", "contentHash": "UTdxWvbgp2xzT1Ajaa2va+Qi3oNHJPasYmVhbKI2VVdu1VYP6yUG+RikhsHvpD7iM0S8e8UYb5Qm/LTWxx9QAA==" }, + "JetBrains.Annotations": { + "type": "Transitive", + "resolved": "2021.1.0", + "contentHash": "n9JSw5Z+F+6gp9vSv4aLH6p/bx3GAYA6FZVq1wJq/TJySv/kPgFKLGFeS7A8Xa5X4/GWorh5gd43yjamUgnBNA==" + }, "LiteDB": { "type": "Transitive", "resolved": "5.0.10", @@ -1472,6 +1477,13 @@ "System.Buffers": "4.5.1", "System.Numerics.Vectors": "4.5.0" } + }, + "artemis.visualscripting": { + "type": "Project", + "dependencies": { + "Artemis.Core": "1.0.0", + "JetBrains.Annotations": "2021.1.0" + } } } } diff --git a/src/Artemis.VisualScripting/Artemis.VisualScripting.csproj b/src/Artemis.VisualScripting/Artemis.VisualScripting.csproj new file mode 100644 index 000000000..86031c078 --- /dev/null +++ b/src/Artemis.VisualScripting/Artemis.VisualScripting.csproj @@ -0,0 +1,52 @@ + + + net5.0-windows + false + false + Artemis.VisualScripting + Artemis Visual-Scripting + Copyright © Darth Affe - 2021 + bin\ + x64 + true + disable + latest + + + + x64 + bin\Artemis.VisualScripting.xml + + 5 + + + + 1.0-{chash:6} + true + true + true + v[0-9]* + true + git + true + + + + bin\Artemis.VisualScripting.xml + + + + + + + + + + + + + $(DefaultXamlRuntime) + Designer + + + \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Attributes/UIAttribute.cs b/src/Artemis.VisualScripting/Attributes/UIAttribute.cs new file mode 100644 index 000000000..86421c9d4 --- /dev/null +++ b/src/Artemis.VisualScripting/Attributes/UIAttribute.cs @@ -0,0 +1,37 @@ +using System; + +namespace Artemis.VisualScripting.Attributes +{ + public class UIAttribute : Attribute + { + #region Properties & Fields + + public string Name { get; } + public string Description { get; set; } + public string Category { get; set; } + + #endregion + + #region Constructors + + public UIAttribute(string name) + { + this.Name = name; + } + + public UIAttribute(string name, string description) + { + this.Name = name; + this.Description = description; + } + + public UIAttribute(string name, string description, string category) + { + this.Name = name; + this.Description = description; + this.Category = category; + } + + #endregion + } +} diff --git a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptCablePresenter.cs b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptCablePresenter.cs new file mode 100644 index 000000000..32de5d316 --- /dev/null +++ b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptCablePresenter.cs @@ -0,0 +1,68 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Shapes; +using Artemis.VisualScripting.Editor.Controls.Wrapper; + +namespace Artemis.VisualScripting.Editor.Controls +{ + [TemplatePart(Name = PART_PATH, Type = typeof(Path))] + public class VisualScriptCablePresenter : Control + { + #region Constants + + private const string PART_PATH = "PART_Path"; + + #endregion + + #region Properties & Fields + + private Path _path; + + #endregion + + #region Dependency Properties + + public static readonly DependencyProperty CableProperty = DependencyProperty.Register( + "Cable", typeof(VisualScriptCable), typeof(VisualScriptCablePresenter), new PropertyMetadata(default(VisualScriptCable))); + + public VisualScriptCable Cable + { + get => (VisualScriptCable)GetValue(CableProperty); + set => SetValue(CableProperty, value); + } + + public static readonly DependencyProperty ThicknessProperty = DependencyProperty.Register( + "Thickness", typeof(double), typeof(VisualScriptCablePresenter), new PropertyMetadata(default(double))); + + public double Thickness + { + get => (double)GetValue(ThicknessProperty); + set => SetValue(ThicknessProperty, value); + } + + #endregion + + #region Methods + + public override void OnApplyTemplate() + { + _path = GetTemplateChild(PART_PATH) as Path ?? throw new NullReferenceException($"The Path '{PART_PATH}' is missing."); + _path.MouseDown += OnPathMouseDown; + } + + private void OnPathMouseDown(object sender, MouseButtonEventArgs args) + { + if ((args.ChangedButton == MouseButton.Left) && (args.LeftButton == MouseButtonState.Pressed) && (args.ClickCount == 2)) + { + //TODO DarthAffe 17.06.2021: Should we add rerouting? + //AddRerouteNode(); + } + else if (args.ChangedButton == MouseButton.Middle) + Cable.Disconnect(); + } + + #endregion + } +} diff --git a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptEditor.cs b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptEditor.cs new file mode 100644 index 000000000..dad4ef715 --- /dev/null +++ b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptEditor.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.Windows; +using System.Windows.Controls; +using Artemis.Core.VisualScripting; +using Artemis.VisualScripting.Model; + +namespace Artemis.VisualScripting.Editor.Controls +{ + public class VisualScriptEditor : Control + { + #region Properties & Fields + + #endregion + + #region Dependency Properties + + public static readonly DependencyProperty ScriptProperty = DependencyProperty.Register( + "Script", typeof(IScript), typeof(VisualScriptEditor), new PropertyMetadata(default(IScript))); + + public IScript Script + { + get => (IScript)GetValue(ScriptProperty); + set => SetValue(ScriptProperty, value); + } + + public static readonly DependencyProperty AvailableNodesProperty = DependencyProperty.Register( + "AvailableNodes", typeof(IEnumerable), typeof(VisualScriptEditor), new PropertyMetadata(default(IEnumerable))); + + public IEnumerable AvailableNodes + { + get => (IEnumerable)GetValue(AvailableNodesProperty); + set => SetValue(AvailableNodesProperty, value); + } + + #endregion + + #region Methods + + #endregion + } +} diff --git a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptNodeCreationBox.cs b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptNodeCreationBox.cs new file mode 100644 index 000000000..6019a7811 --- /dev/null +++ b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptNodeCreationBox.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections; +using System.ComponentModel; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Input; +using Artemis.VisualScripting.Model; + +namespace Artemis.VisualScripting.Editor.Controls +{ + [TemplatePart(Name = PART_SEARCHBOX, Type = typeof(TextBox))] + [TemplatePart(Name = PART_CONTENT, Type = typeof(ListBox))] + public class VisualScriptNodeCreationBox : Control + { + #region Constants + + private const string PART_SEARCHBOX = "PART_SearchBox"; + private const string PART_CONTENT = "PART_Content"; + + #endregion + + #region Properties & Fields + + private TextBox _searchBox; + private ListBox _contentList; + + private CollectionViewSource _collectionViewSource; + private ICollectionView _contentView; + + #endregion + + #region DependencyProperties + + public static readonly DependencyProperty AvailableNodesProperty = DependencyProperty.Register( + "AvailableNodes", typeof(IEnumerable), typeof(VisualScriptNodeCreationBox), new PropertyMetadata(default(IEnumerable), OnItemsSourceChanged)); + + public IEnumerable AvailableNodes + { + get => (IEnumerable)GetValue(AvailableNodesProperty); + set => SetValue(AvailableNodesProperty, value); + } + + public static readonly DependencyProperty CreateNodeCommandProperty = DependencyProperty.Register( + "CreateNodeCommand", typeof(ICommand), typeof(VisualScriptNodeCreationBox), new PropertyMetadata(default(ICommand))); + + public ICommand CreateNodeCommand + { + get => (ICommand)GetValue(CreateNodeCommandProperty); + set => SetValue(CreateNodeCommandProperty, value); + } + + #endregion + + #region Methods + + public override void OnApplyTemplate() + { + _searchBox = GetTemplateChild(PART_SEARCHBOX) as TextBox ?? throw new NullReferenceException($"The Element '{PART_SEARCHBOX}' is missing."); + _contentList = GetTemplateChild(PART_CONTENT) as ListBox ?? throw new NullReferenceException($"The Element '{PART_CONTENT}' is missing."); + + _searchBox.TextChanged += OnSearchBoxTextChanged; + _contentList.IsSynchronizedWithCurrentItem = false; + _contentList.SelectionChanged += OnContentListSelectionChanged; + _contentList.SelectionMode = SelectionMode.Single; + + _searchBox.Focus(); + _contentView?.Refresh(); + ItemsSourceChanged(); + } + + private void OnSearchBoxTextChanged(object sender, TextChangedEventArgs args) + { + _contentView?.Refresh(); + } + + private void OnContentListSelectionChanged(object sender, SelectionChangedEventArgs args) + { + if ((args == null) || (_contentList?.SelectedItem == null)) return; + + CreateNodeCommand?.Execute(_contentList.SelectedItem); + } + + private bool Filter(object o) + { + if (_searchBox == null) return false; + if (o is not NodeData nodeData) return false; + + return nodeData.Name.Contains(_searchBox.Text, StringComparison.OrdinalIgnoreCase); + } + + private void ItemsSourceChanged() + { + if (_contentList == null) return; + + if (AvailableNodes == null) + { + _contentView = null; + _collectionViewSource = null; + } + else + { + _collectionViewSource = new CollectionViewSource { Source = AvailableNodes, SortDescriptions = { new SortDescription("Name", ListSortDirection.Ascending)}}; + _contentView = _collectionViewSource.View; + _contentView.Filter += Filter; + } + + _contentList.ItemsSource = _contentView; + } + + private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs args) => (d as VisualScriptNodeCreationBox)?.ItemsSourceChanged(); + + #endregion + } +} diff --git a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptNodePresenter.cs b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptNodePresenter.cs new file mode 100644 index 000000000..e79a195de --- /dev/null +++ b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptNodePresenter.cs @@ -0,0 +1,137 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using Artemis.VisualScripting.Editor.Controls.Wrapper; + +namespace Artemis.VisualScripting.Editor.Controls +{ + public class VisualScriptNodePresenter : Control + { + #region Properties & Fields + + private bool _isDragging; + private bool _isDragged; + private bool _isDeselected; + private Point _dragStartPosition; + + #endregion + + #region Dependency Properties + + public static readonly DependencyProperty NodeProperty = DependencyProperty.Register( + "Node", typeof(VisualScriptNode), typeof(VisualScriptNodePresenter), new PropertyMetadata(default(VisualScriptNode))); + + public VisualScriptNode Node + { + get => (VisualScriptNode)GetValue(NodeProperty); + set => SetValue(NodeProperty, value); + } + + public static readonly DependencyProperty TitleBrushProperty = DependencyProperty.Register( + "TitleBrush", typeof(Brush), typeof(VisualScriptNodePresenter), new PropertyMetadata(default(Brush))); + + public Brush TitleBrush + { + get => (Brush)GetValue(TitleBrushProperty); + set => SetValue(TitleBrushProperty, value); + } + + #endregion + + #region Methods + + protected override Size MeasureOverride(Size constraint) + { + int SnapToGridSize(int value) + { + int mod = value % Node.Script.GridSize; + return mod switch + { + < 0 => Node.Script.GridSize, + > 0 => value + (Node.Script.GridSize - mod), + _ => value + }; + } + + Size neededSize = base.MeasureOverride(constraint); + int width = (int)Math.Ceiling(neededSize.Width); + int height = (int)Math.Ceiling(neededSize.Height); + + return new Size(SnapToGridSize(width), SnapToGridSize(height)); + } + + protected override void OnMouseLeftButtonDown(MouseButtonEventArgs args) + { + base.OnMouseLeftButtonDown(args); + + _isDragged = false; + _isDragging = true; + _isDeselected = false; + + bool isShiftDown = Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift); + if (!Node.IsSelected) + Node.Select(isShiftDown); + else if (isShiftDown) + { + _isDeselected = true; + Node.Deselect(true); + } + + Node.DragStart(); + + _dragStartPosition = PointToScreen(args.GetPosition(this)); + + CaptureMouse(); + args.Handled = true; + } + + protected override void OnMouseLeftButtonUp(MouseButtonEventArgs args) + { + base.OnMouseLeftButtonUp(args); + + if (_isDragging) + { + _isDragging = false; + Node.DragEnd(); + + ReleaseMouseCapture(); + } + + if (!_isDragged && !_isDeselected) + Node.Select(Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)); + + args.Handled = true; + } + + protected override void OnMouseMove(MouseEventArgs args) + { + base.OnMouseMove(args); + + if (!_isDragging) return; + + Point mousePosition = PointToScreen(args.GetPosition(this)); + Vector offset = mousePosition - _dragStartPosition; + + if (args.LeftButton == MouseButtonState.Pressed) + { + _dragStartPosition = mousePosition; + Node.DragMove(offset.X, offset.Y); + + if ((offset.X != 0) && (offset.Y != 0)) + _isDragged = true; + } + else + { + _isDragging = false; + Node.DragEnd(); + ReleaseMouseCapture(); + } + + args.Handled = true; + } + + #endregion + } +} diff --git a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPinPresenter.cs b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPinPresenter.cs new file mode 100644 index 000000000..75931bc08 --- /dev/null +++ b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPinPresenter.cs @@ -0,0 +1,184 @@ +using System; +using System.ComponentModel; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using Artemis.VisualScripting.Editor.Controls.Wrapper; +using Artemis.VisualScripting.Model; + +namespace Artemis.VisualScripting.Editor.Controls +{ + [TemplatePart(Name = PART_DOT, Type = typeof(FrameworkElement))] + public class VisualScriptPinPresenter : Control + { + #region Constants + + private const string PART_DOT = "PART_Dot"; + + #endregion + + #region Properties & Fields + + private VisualScriptNodePresenter _nodePresenter; + private FrameworkElement _dot; + + #endregion + + #region Dependency Properties + + public static readonly DependencyProperty PinProperty = DependencyProperty.Register( + "Pin", typeof(VisualScriptPin), typeof(VisualScriptPinPresenter), new PropertyMetadata(default(VisualScriptPin), PinChanged)); + + public VisualScriptPin Pin + { + get => (VisualScriptPin)GetValue(PinProperty); + set => SetValue(PinProperty, value); + } + + #endregion + + #region Constructors + + public VisualScriptPinPresenter() + { + Loaded += OnLoaded; + Unloaded += OnUnloaded; + } + + #endregion + + #region Methods + + private void OnLoaded(object sender, RoutedEventArgs args) + { + DependencyObject parent = this; + while ((parent = VisualTreeHelper.GetParent(parent)) != null) + { + if (parent is VisualScriptNodePresenter nodePresenter) + { + _nodePresenter = nodePresenter; + break; + } + } + + LayoutUpdated += OnLayoutUpdated; + + UpdateAbsoluteLocation(); + } + + private void OnUnloaded(object sender, RoutedEventArgs e) + { + LayoutUpdated -= OnLayoutUpdated; + _nodePresenter = null; + } + + private void OnNodePropertyChanged(object sender, PropertyChangedEventArgs e) + { + UpdateAbsoluteLocation(); + } + + private void OnLayoutUpdated(object sender, EventArgs args) + { + _dot = GetTemplateChild(PART_DOT) as FrameworkElement ?? throw new NullReferenceException($"The Element '{PART_DOT}' is missing."); + + _dot.AllowDrop = true; + + _dot.MouseDown += OnDotMouseDown; + _dot.MouseMove += OnDotMouseMove; + _dot.Drop += OnDotDrop; + _dot.DragEnter += OnDotDrag; + _dot.DragOver += OnDotDrag; + + UpdateAbsoluteLocation(); + } + + private static void PinChanged(DependencyObject d, DependencyPropertyChangedEventArgs args) + { + if (d is not VisualScriptPinPresenter presenter) return; + + presenter.PinChanged(args); + } + + private void PinChanged(DependencyPropertyChangedEventArgs args) + { + if (args.OldValue is VisualScriptPin oldPin) + oldPin.Node.Node.PropertyChanged -= OnNodePropertyChanged; + + if (args.NewValue is VisualScriptPin newPin) + newPin.Node.Node.PropertyChanged += OnNodePropertyChanged; + + UpdateAbsoluteLocation(); + } + + private void UpdateAbsoluteLocation() + { + if ((Pin == null) || (_nodePresenter == null)) return; + + try + { + double circleRadius = ActualHeight / 2.0; + double xOffset = Pin.Pin.Direction == PinDirection.Input ? circleRadius : ActualWidth - circleRadius; + Point relativePosition = this.TransformToVisual(_nodePresenter).Transform(new Point(xOffset, circleRadius)); + Pin.AbsolutePosition = new Point(Pin.Node.X + relativePosition.X, Pin.Node.Y + relativePosition.Y); + } + catch + { + Pin.AbsolutePosition = new Point(0, 0); + } + } + + private void OnDotMouseDown(object sender, MouseButtonEventArgs args) + { + if (args.ChangedButton == MouseButton.Middle) + Pin.DisconnectAll(); + + args.Handled = true; + } + + private void OnDotMouseMove(object sender, MouseEventArgs args) + { + if (args.LeftButton == MouseButtonState.Pressed) + { + Pin.SetConnecting(true); + DragDrop.DoDragDrop(this, Pin, DragDropEffects.Link); + Pin.SetConnecting(false); + + args.Handled = true; + } + } + + private void OnDotDrag(object sender, DragEventArgs args) + { + if (!args.Data.GetDataPresent(typeof(VisualScriptPin))) + args.Effects = DragDropEffects.None; + else + { + VisualScriptPin sourcePin = (VisualScriptPin)args.Data.GetData(typeof(VisualScriptPin)); + if (sourcePin == null) + args.Effects = DragDropEffects.None; + else + args.Effects = ((sourcePin.Pin.Direction != Pin.Pin.Direction) && (sourcePin.Pin.Node != Pin.Pin.Node) && IsTypeCompatible(sourcePin.Pin.Type)) ? DragDropEffects.Link : DragDropEffects.None; + } + + if (args.Effects == DragDropEffects.None) + args.Handled = true; + } + + private void OnDotDrop(object sender, DragEventArgs args) + { + if (!args.Data.GetDataPresent(typeof(VisualScriptPin))) return; + + VisualScriptPin sourcePin = (VisualScriptPin)args.Data.GetData(typeof(VisualScriptPin)); + if ((sourcePin == null) || !IsTypeCompatible(sourcePin.Pin.Type)) return; + + try { new VisualScriptCable(Pin, sourcePin); } catch { /**/ } + + args.Handled = true; + } + + private bool IsTypeCompatible(Type type) => ((Pin.Pin.Direction == PinDirection.Input) && (Pin.Pin.Type == typeof(object))) || (Pin.Pin.Type == type); + + #endregion + } +} diff --git a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPresenter.cs b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPresenter.cs new file mode 100644 index 000000000..327c96be9 --- /dev/null +++ b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPresenter.cs @@ -0,0 +1,437 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using Artemis.Core.VisualScripting; +using Artemis.VisualScripting.Editor.Controls.Wrapper; +using Artemis.VisualScripting.Model; +using Artemis.VisualScripting.ViewModel; + +namespace Artemis.VisualScripting.Editor.Controls +{ + [TemplatePart(Name = PART_CANVAS, Type = typeof(Canvas))] + [TemplatePart(Name = PART_NODELIST, Type = typeof(ItemsControl))] + [TemplatePart(Name = PART_CABLELIST, Type = typeof(ItemsControl))] + [TemplatePart(Name = PART_SELECTION_BORDER, Type = typeof(Border))] + [TemplatePart(Name = PART_CREATION_BOX_PARENT, Type = typeof(Panel))] + public class VisualScriptPresenter : Control + { + #region Constants + + private const string PART_CANVAS = "PART_Canvas"; + private const string PART_NODELIST = "PART_NodeList"; + private const string PART_CABLELIST = "PART_CableList"; + private const string PART_SELECTION_BORDER = "PART_SelectionBorder"; + private const string PART_CREATION_BOX_PARENT = "PART_CreationBoxParent"; + + #endregion + + #region Properties & Fields + + private Canvas _canvas; + private ItemsControl _nodeList; + private ItemsControl _cableList; + private Border _selectionBorder; + private TranslateTransform _canvasViewPortTransform; + private Panel _creationBoxParent; + + private Vector _viewportCenter = new(0, 0); + + private bool _dragCanvas = false; + private Point _dragCanvasStartLocation; + private Vector _dragCanvasStartOffset; + private bool _movedDuringDrag = false; + + private bool _boxSelect = false; + private Point _boxSelectStartPoint; + + private Point _lastRightClickLocation; + + internal VisualScript VisualScript { get; private set; } + + #endregion + + #region Dependency Properties + + public static readonly DependencyProperty ScriptProperty = DependencyProperty.Register( + "Script", typeof(IScript), typeof(VisualScriptPresenter), new PropertyMetadata(default(IScript), ScriptChanged)); + + public IScript Script + { + get => (IScript)GetValue(ScriptProperty); + set => SetValue(ScriptProperty, value); + } + + public static readonly DependencyProperty ScaleProperty = DependencyProperty.Register( + "Scale", typeof(double), typeof(VisualScriptPresenter), new PropertyMetadata(1.0, ScaleChanged)); + + public double Scale + { + get => (double)GetValue(ScaleProperty); + set => SetValue(ScaleProperty, value); + } + + public static readonly DependencyProperty MinScaleProperty = DependencyProperty.Register( + "MinScale", typeof(double), typeof(VisualScriptPresenter), new PropertyMetadata(0.15)); + + public double MinScale + { + get => (double)GetValue(MinScaleProperty); + set => SetValue(MinScaleProperty, value); + } + + public static readonly DependencyProperty MaxScaleProperty = DependencyProperty.Register( + "MaxScale", typeof(double), typeof(VisualScriptPresenter), new PropertyMetadata(1.0)); + + public double MaxScale + { + get => (double)GetValue(MaxScaleProperty); + set => SetValue(MaxScaleProperty, value); + } + + public static readonly DependencyProperty ScaleFactorProperty = DependencyProperty.Register( + "ScaleFactor", typeof(double), typeof(VisualScriptPresenter), new PropertyMetadata(1.3)); + + public double ScaleFactor + { + get => (double)GetValue(ScaleFactorProperty); + set => SetValue(ScaleFactorProperty, value); + } + + public static readonly DependencyProperty AvailableNodesProperty = DependencyProperty.Register( + "AvailableNodes", typeof(IEnumerable), typeof(VisualScriptPresenter), new PropertyMetadata(default(IEnumerable))); + + public IEnumerable AvailableNodes + { + get => (IEnumerable)GetValue(AvailableNodesProperty); + set => SetValue(AvailableNodesProperty, value); + } + + public static readonly DependencyProperty CreateNodeCommandProperty = DependencyProperty.Register( + "CreateNodeCommand", typeof(ICommand), typeof(VisualScriptPresenter), new PropertyMetadata(default(ICommand))); + + public ICommand CreateNodeCommand + { + get => (ICommand)GetValue(CreateNodeCommandProperty); + private set => SetValue(CreateNodeCommandProperty, value); + } + + public static readonly DependencyProperty GridSizeProperty = DependencyProperty.Register( + "GridSize", typeof(int), typeof(VisualScriptPresenter), new PropertyMetadata(24)); + + public int GridSize + { + get => (int)GetValue(GridSizeProperty); + set => SetValue(GridSizeProperty, value); + } + + public static readonly DependencyProperty SurfaceSizeProperty = DependencyProperty.Register( + "SurfaceSize", typeof(int), typeof(VisualScriptPresenter), new PropertyMetadata(16384)); + + public int SurfaceSize + { + get => (int)GetValue(SurfaceSizeProperty); + set => SetValue(SurfaceSizeProperty, value); + } + + #endregion + + #region Constructors + + public VisualScriptPresenter() + { + CreateNodeCommand = new ActionCommand(CreateNode); + + this.SizeChanged += OnSizeChanged; + } + + #endregion + + #region Methods + + public override void OnApplyTemplate() + { + _canvas = GetTemplateChild(PART_CANVAS) as Canvas ?? throw new NullReferenceException($"The Canvas '{PART_CANVAS}' is missing."); + _selectionBorder = GetTemplateChild(PART_SELECTION_BORDER) as Border ?? throw new NullReferenceException($"The Border '{PART_SELECTION_BORDER}' is missing."); + _nodeList = GetTemplateChild(PART_NODELIST) as ItemsControl ?? throw new NullReferenceException($"The ItemsControl '{PART_NODELIST}' is missing."); + _cableList = GetTemplateChild(PART_CABLELIST) as ItemsControl ?? throw new NullReferenceException($"The ItemsControl '{PART_CABLELIST}' is missing."); + _creationBoxParent = GetTemplateChild(PART_CREATION_BOX_PARENT) as Panel ?? throw new NullReferenceException($"The Panel '{PART_CREATION_BOX_PARENT}' is missing."); + + _canvas.AllowDrop = true; + + _canvas.RenderTransform = _canvasViewPortTransform = new TranslateTransform(0, 0); + _canvas.MouseLeftButtonDown += OnCanvasMouseLeftButtonDown; + _canvas.MouseLeftButtonUp += OnCanvasMouseLeftButtonUp; + _canvas.MouseRightButtonDown += OnCanvasMouseRightButtonDown; + _canvas.MouseRightButtonUp += OnCanvasMouseRightButtonUp; + _canvas.PreviewMouseRightButtonDown += OnCanvasPreviewMouseRightButtonDown; + _canvas.MouseMove += OnCanvasMouseMove; + _canvas.MouseWheel += OnCanvasMouseWheel; + _canvas.DragOver += OnCanvasDragOver; + + _nodeList.ItemsSource = VisualScript?.Nodes; + _cableList.ItemsSource = VisualScript?.Cables; + } + + private void OnSizeChanged(object sender, SizeChangedEventArgs args) + { + if (sender is not VisualScriptPresenter scriptPresenter) return; + + scriptPresenter.UpdatePanning(); + } + + private static void ScriptChanged(DependencyObject d, DependencyPropertyChangedEventArgs args) + { + if (d is not VisualScriptPresenter scriptPresenter) return; + + scriptPresenter.ScriptChanged(args.NewValue is not Script script ? null : new VisualScript(script, scriptPresenter.SurfaceSize, scriptPresenter.GridSize)); + } + + private void ScriptChanged(VisualScript newScript) + { + if (VisualScript != null) + VisualScript.PropertyChanged -= OnVisualScriptPropertyChanged; + + VisualScript = newScript; + + if (VisualScript != null) + { + VisualScript.PropertyChanged += OnVisualScriptPropertyChanged; + + if (_nodeList != null) + _nodeList.ItemsSource = VisualScript?.Nodes; + + if (_cableList != null) + _cableList.ItemsSource = VisualScript?.Cables; + + VisualScript.Nodes.Clear(); + foreach (INode node in VisualScript.Script.Nodes) + InitializeNode(node); + } + + CenterAt(new Vector(0, 0)); + } + + private void OnVisualScriptPropertyChanged(object sender, PropertyChangedEventArgs args) + { + if (args.PropertyName == nameof(VisualScript.Cables)) + _cableList.ItemsSource = VisualScript.Cables; + } + + private void OnCanvasPreviewMouseRightButtonDown(object sender, MouseButtonEventArgs args) + { + _lastRightClickLocation = args.GetPosition(_canvas); + } + + private void OnCanvasMouseLeftButtonDown(object sender, MouseButtonEventArgs args) + { + VisualScript.DeselectAllNodes(); + + _boxSelect = true; + _boxSelectStartPoint = args.GetPosition(_canvas); + Canvas.SetLeft(_selectionBorder, _boxSelectStartPoint.X); + Canvas.SetTop(_selectionBorder, _boxSelectStartPoint.Y); + _selectionBorder.Width = 0; + _selectionBorder.Height = 0; + _selectionBorder.Visibility = Visibility.Visible; + _canvas.CaptureMouse(); + + args.Handled = true; + } + + private void OnCanvasMouseLeftButtonUp(object sender, MouseButtonEventArgs args) + { + if (_boxSelect) + { + _boxSelect = false; + _canvas.ReleaseMouseCapture(); + Mouse.OverrideCursor = null; + _selectionBorder.Visibility = Visibility.Hidden; + SelectWithinRectangle(new Rect(Canvas.GetLeft(_selectionBorder), Canvas.GetTop(_selectionBorder), _selectionBorder.Width, _selectionBorder.Height)); + args.Handled = _movedDuringDrag; + } + } + + private void OnCanvasMouseRightButtonDown(object sender, MouseButtonEventArgs args) + { + _dragCanvas = true; + _dragCanvasStartLocation = args.GetPosition(this); + _dragCanvasStartOffset = _viewportCenter; + + _movedDuringDrag = false; + + _canvas.CaptureMouse(); + + args.Handled = true; + } + + private void OnCanvasMouseRightButtonUp(object sender, MouseButtonEventArgs args) + { + if (_dragCanvas) + { + _dragCanvas = false; + _canvas.ReleaseMouseCapture(); + Mouse.OverrideCursor = null; + args.Handled = _movedDuringDrag; + } + } + + private void OnCanvasMouseMove(object sender, MouseEventArgs args) + { + if (_dragCanvas) + { + if (args.RightButton == MouseButtonState.Pressed) + { + Vector newLocation = _dragCanvasStartOffset + (((args.GetPosition(this) - _dragCanvasStartLocation)) * (1.0 / Scale)); + CenterAt(newLocation); + + _movedDuringDrag = true; + Mouse.OverrideCursor = Cursors.ScrollAll; + } + else + _dragCanvas = false; + + args.Handled = true; + } + else if (_boxSelect) + { + if (args.LeftButton == MouseButtonState.Pressed) + { + double x = _boxSelectStartPoint.X; + double y = _boxSelectStartPoint.Y; + Point mousePosition = args.GetPosition(_canvas); + + double rectX = mousePosition.X > x ? x : mousePosition.X; + double rectY = mousePosition.Y > y ? y : mousePosition.Y; + double rectWidth = Math.Abs(x - mousePosition.X); + double rectHeight = Math.Abs(y - mousePosition.Y); + + Canvas.SetLeft(_selectionBorder, rectX); + Canvas.SetTop(_selectionBorder, rectY); + _selectionBorder.Width = rectWidth; + _selectionBorder.Height = rectHeight; + } + else + _boxSelect = false; + + args.Handled = true; + } + } + + private void OnCanvasDragOver(object sender, DragEventArgs args) + { + if (VisualScript == null) return; + + if (VisualScript.IsConnecting) + VisualScript.OnDragOver(args.GetPosition(_canvas)); + } + + private void OnCanvasMouseWheel(object sender, MouseWheelEventArgs args) + { + if (args.Delta < 0) + Scale /= ScaleFactor; + else + Scale *= ScaleFactor; + + Scale = Clamp(Scale, MinScale, MaxScale); + + _canvas.LayoutTransform = new ScaleTransform(Scale, Scale); + + UpdatePanning(); + + args.Handled = true; + } + + private void SelectWithinRectangle(Rect rectangle) + { + if (Script == null) return; + + VisualScript.DeselectAllNodes(); + + for (int i = 0; i < _nodeList.Items.Count; i++) + { + ContentPresenter nodeControl = (ContentPresenter)_nodeList.ItemContainerGenerator.ContainerFromIndex(i); + VisualScriptNode node = (VisualScriptNode)nodeControl.Content; + + double nodeWidth = nodeControl.ActualWidth; + double nodeHeight = nodeControl.ActualHeight; + + if (rectangle.IntersectsWith(new Rect(node.X, node.Y, nodeWidth, nodeHeight))) + node.Select(true); + } + } + + private static void ScaleChanged(DependencyObject d, DependencyPropertyChangedEventArgs args) + { + if (d is not VisualScriptPresenter presenter) return; + if (presenter.VisualScript == null) return; + + presenter.VisualScript.NodeDragScale = 1.0 / presenter.Scale; + } + + private void CreateNode(NodeData nodeData) + { + if (nodeData == null) return; + + if (_creationBoxParent.ContextMenu != null) + _creationBoxParent.ContextMenu.IsOpen = false; + + INode node = nodeData.CreateNode(); + Script.AddNode(node); + + InitializeNode(node, _lastRightClickLocation); + } + + private void InitializeNode(INode node, Point? initialLocation = null) + { + VisualScriptNode visualScriptNode = new(VisualScript, node); + if (initialLocation != null) + { + visualScriptNode.X = initialLocation.Value.X; + visualScriptNode.Y = initialLocation.Value.Y; + } + visualScriptNode.SnapNodeToGrid(); + VisualScript.Nodes.Add(visualScriptNode); + } + + private void CenterAt(Vector vector) + { + double halfSurface = (SurfaceSize / 2.0); + _viewportCenter = Clamp(vector, new Vector(-halfSurface, -halfSurface), new Vector(halfSurface, halfSurface)); + UpdatePanning(); + } + + private void UpdatePanning() + { + if (_canvasViewPortTransform == null) return; + + double surfaceOffset = (SurfaceSize / 2.0) * Scale; + _canvasViewPortTransform.X = (((_viewportCenter.X * Scale) + (ActualWidth / 2.0))) - surfaceOffset; + _canvasViewPortTransform.Y = (((_viewportCenter.Y * Scale) + (ActualHeight / 2.0))) - surfaceOffset; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static double Clamp(double value, double min, double max) + { + // ReSharper disable ConvertIfStatementToReturnStatement - I'm not sure why, but inlining this statement reduces performance by ~10% + if (value < min) return min; + if (value > max) return max; + return value; + // ReSharper restore ConvertIfStatementToReturnStatement + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector Clamp(Vector value, Vector min, Vector max) + { + double x = Clamp(value.X, min.X, max.X); + double y = Clamp(value.Y, min.Y, max.Y); + return new Vector(x, y); + } + + #endregion + } +} diff --git a/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScript.cs b/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScript.cs new file mode 100644 index 000000000..57fdaaa5b --- /dev/null +++ b/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScript.cs @@ -0,0 +1,189 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; +using System.Windows; +using Artemis.Core.VisualScripting; +using Artemis.VisualScripting.Events; +using Artemis.VisualScripting.ViewModel; + +namespace Artemis.VisualScripting.Editor.Controls.Wrapper +{ + public class VisualScript : AbstractBindable + { + #region Properties & Fields + + private readonly HashSet _selectedNodes = new(); + private readonly Dictionary _nodeStartPositions = new(); + private double _nodeDragAccumulationX; + private double _nodeDragAccumulationY; + + public IScript Script { get; } + + public int SurfaceSize { get; } + public int GridSize { get; } + + private double _nodeDragScale = 1; + public double NodeDragScale + { + get => _nodeDragScale; + set => SetProperty(ref _nodeDragScale, value); + } + + private VisualScriptPin _isConnectingPin; + private VisualScriptPin IsConnectingPin + { + get => _isConnectingPin; + set + { + if (SetProperty(ref _isConnectingPin, value)) + OnPropertyChanged(nameof(IsConnecting)); + } + } + + public ObservableCollection Nodes { get; } = new(); + + public IEnumerable Cables => Nodes.SelectMany(n => n.InputPins.SelectMany(p => p.Connections)) + .Concat(Nodes.SelectMany(n => n.OutputPins.SelectMany(p => p.Connections))) + .Concat(Nodes.SelectMany(n => n.InputPinCollections.SelectMany(p => p.Pins).SelectMany(p => p.Connections))) + .Concat(Nodes.SelectMany(n => n.OutputPinCollections.SelectMany(p => p.Pins).SelectMany(p => p.Connections))) + .Distinct(); + + public bool IsConnecting => IsConnectingPin != null; + + #endregion + + #region Constructors + + public VisualScript(IScript script, int surfaceSize, int gridSize) + { + this.Script = script; + this.SurfaceSize = surfaceSize; + this.GridSize = gridSize; + + Nodes.CollectionChanged += OnNodeCollectionChanged; + } + + #endregion + + #region Methods + + public void DeselectAllNodes(VisualScriptNode except = null) + { + List selectedNodes = _selectedNodes.ToList(); + foreach (VisualScriptNode node in selectedNodes.Where(n => n != except)) + node.Deselect(); + } + + private void OnNodeCollectionChanged(object sender, NotifyCollectionChangedEventArgs args) + { + if (args.OldItems != null) + UnregisterNodes(args.OldItems.Cast()); + + if (args.NewItems != null) + RegisterNodes(args.NewItems.Cast()); + } + + private void RegisterNodes(IEnumerable nodes) + { + foreach (VisualScriptNode node in nodes) + { + node.IsSelectedChanged += OnNodeIsSelectedChanged; + node.DragStarting += OnNodeDragStarting; + node.DragEnding += OnNodeDragEnding; + node.DragMoving += OnNodeDragMoving; + + if (node.IsSelected) + _selectedNodes.Add(node); + } + } + + private void UnregisterNodes(IEnumerable nodes) + { + foreach (VisualScriptNode node in nodes) + { + node.IsSelectedChanged -= OnNodeIsSelectedChanged; + node.DragStarting -= OnNodeDragStarting; + node.DragEnding -= OnNodeDragEnding; + node.DragMoving -= OnNodeDragMoving; + + _selectedNodes.Remove(node); + } + } + + private void OnNodeIsSelectedChanged(object sender, VisualScriptNodeIsSelectedChangedEventArgs args) + { + if (sender is not VisualScriptNode node) return; + + if (args.IsSelected) + { + if (!args.AlterSelection) + DeselectAllNodes(node); + + _selectedNodes.Add(node); + } + else + _selectedNodes.Remove(node); + } + + private void OnNodeDragStarting(object sender, EventArgs args) + { + _nodeDragAccumulationX = 0; + _nodeDragAccumulationY = 0; + _nodeStartPositions.Clear(); + + foreach (VisualScriptNode node in _selectedNodes) + _nodeStartPositions.Add(node, (node.X, node.Y)); + } + + private void OnNodeDragEnding(object sender, EventArgs args) + { + foreach (VisualScriptNode node in _selectedNodes) + node.SnapNodeToGrid(); + } + + private void OnNodeDragMoving(object sender, VisualScriptNodeDragMovingEventArgs args) + { + _nodeDragAccumulationX += args.DX * NodeDragScale; + _nodeDragAccumulationY += args.DY * NodeDragScale; + + foreach (VisualScriptNode node in _selectedNodes) + { + node.X = _nodeStartPositions[node].X + _nodeDragAccumulationX; + node.Y = _nodeStartPositions[node].Y + _nodeDragAccumulationY; + node.SnapNodeToGrid(); + } + } + + internal void OnDragOver(Point position) + { + if (IsConnectingPin == null) return; + + IsConnectingPin.AbsolutePosition = position; + } + + internal void OnIsConnectingPinChanged(VisualScriptPin isConnectingPin) + { + IsConnectingPin = isConnectingPin; + } + + internal void OnPinConnected(PinConnectedEventArgs args) + { + OnPropertyChanged(nameof(Cables)); + } + + internal void OnPinDisconnected(PinDisconnectedEventArgs args) + { + OnPropertyChanged(nameof(Cables)); + } + + public void RemoveNode(VisualScriptNode node) + { + Nodes.Remove(node); + Script.RemoveNode(node.Node); + } + + #endregion + } +} diff --git a/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptCable.cs b/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptCable.cs new file mode 100644 index 000000000..9fe3943ae --- /dev/null +++ b/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptCable.cs @@ -0,0 +1,67 @@ +using System; +using Artemis.Core.VisualScripting; +using Artemis.VisualScripting.Model; +using Artemis.VisualScripting.ViewModel; + +namespace Artemis.VisualScripting.Editor.Controls.Wrapper +{ + public class VisualScriptCable : AbstractBindable + { + #region Properties & Fields + + private VisualScriptPin _from; + public VisualScriptPin From + { + get => _from; + private set => SetProperty(ref _from, value); + } + + private VisualScriptPin _to; + public VisualScriptPin To + { + get => _to; + private set => SetProperty(ref _to, value); + } + + #endregion + + #region Constructors + + public VisualScriptCable(VisualScriptPin pin1, VisualScriptPin pin2) + { + if ((pin1.Pin.Direction == PinDirection.Input) && (pin2.Pin.Direction == PinDirection.Input)) + throw new ArgumentException("Can't connect two input pins."); + + if ((pin1.Pin.Direction == PinDirection.Output) && (pin2.Pin.Direction == PinDirection.Output)) + throw new ArgumentException("Can't connect two output pins."); + + From = pin1.Pin.Direction == PinDirection.Output ? pin1 : pin2; + To = pin1.Pin.Direction == PinDirection.Input ? pin1 : pin2; + + From.ConnectTo(this); + To.ConnectTo(this); + } + + #endregion + + #region Methods + + internal void Disconnect() + { + From?.Disconnect(this); + To?.Disconnect(this); + + From = null; + To = null; + } + + internal IPin GetConnectedPin(IPin pin) + { + if (From.Pin == pin) return To.Pin; + if (To.Pin == pin) return From.Pin; + return null; + } + + #endregion + } +} diff --git a/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptNode.cs b/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptNode.cs new file mode 100644 index 000000000..40bf11d6c --- /dev/null +++ b/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptNode.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.ObjectModel; +using Artemis.Core.VisualScripting; +using Artemis.VisualScripting.Events; +using Artemis.VisualScripting.Internal; +using Artemis.VisualScripting.Model; +using Artemis.VisualScripting.ViewModel; + +namespace Artemis.VisualScripting.Editor.Controls.Wrapper +{ + public class VisualScriptNode : AbstractBindable + { + #region Properties & Fields + + private double _locationOffset; + + public VisualScript Script { get; } + public INode Node { get; } + + private bool _isSelected; + public bool IsSelected + { + get => _isSelected; + private set => SetProperty(ref _isSelected, value); + } + + private ObservableCollection _inputPins = new(); + public ObservableCollection InputPins + { + get => _inputPins; + private set => SetProperty(ref _inputPins, value); + } + + private ObservableCollection _outputPins = new(); + public ObservableCollection OutputPins + { + get => _outputPins; + private set => SetProperty(ref _outputPins, value); + } + + private ObservableCollection _inputPinCollections = new(); + public ObservableCollection InputPinCollections + { + get => _inputPinCollections; + private set => SetProperty(ref _inputPinCollections, value); + } + + private ObservableCollection _outputPinCollections = new(); + public ObservableCollection OutputPinCollections + { + get => _outputPinCollections; + private set => SetProperty(ref _outputPinCollections, value); + } + + public double X + { + get => Node.X + _locationOffset; + set + { + Node.X = value - _locationOffset; + OnPropertyChanged(); + } + } + + public double Y + { + get => Node.Y + _locationOffset; + set + { + Node.Y = value - _locationOffset; + OnPropertyChanged(); + } + } + + #endregion + + #region Commands + + private ActionCommand _removeCommand; + public ActionCommand RemoveCommand => _removeCommand ??= new ActionCommand(Remove, RemoveCanExecute); + + #endregion + + #region Events + + public event EventHandler IsSelectedChanged; + public event EventHandler DragStarting; + public event EventHandler DragEnding; + public event EventHandler DragMoving; + + #endregion + + #region Constructors + + public VisualScriptNode(VisualScript script, INode node) + { + this.Script = script; + this.Node = node; + + _locationOffset = script.SurfaceSize / 2.0; + + foreach (IPin pin in node.Pins) + { + if (pin.Direction == PinDirection.Input) + InputPins.Add(new VisualScriptPin(this, pin)); + else + OutputPins.Add(new VisualScriptPin(this, pin)); + } + + foreach (IPinCollection pinCollection in node.PinCollections) + { + if (pinCollection.Direction == PinDirection.Input) + InputPinCollections.Add(new VisualScriptPinCollection(this, pinCollection)); + else + OutputPinCollections.Add(new VisualScriptPinCollection(this, pinCollection)); + } + } + + #endregion + + #region Methods + + public void SnapNodeToGrid() + { + X -= X % Script.GridSize; + Y -= Y % Script.GridSize; + } + + public void Select(bool alterSelection = false) + { + IsSelected = true; + OnIsSelectedChanged(IsSelected, alterSelection); + } + + public void Deselect(bool alterSelection = false) + { + IsSelected = false; + OnIsSelectedChanged(IsSelected, alterSelection); + } + + public void DragStart() => DragStarting?.Invoke(this, new EventArgs()); + public void DragEnd() => DragEnding?.Invoke(this, new EventArgs()); + public void DragMove(double dx, double dy) => DragMoving?.Invoke(this, new VisualScriptNodeDragMovingEventArgs(dx, dy)); + + private void OnIsSelectedChanged(bool isSelected, bool alterSelection) => IsSelectedChanged?.Invoke(this, new VisualScriptNodeIsSelectedChangedEventArgs(isSelected, alterSelection)); + + internal void OnPinConnected(PinConnectedEventArgs args) => Script.OnPinConnected(args); + internal void OnPinDisconnected(PinDisconnectedEventArgs args) => Script.OnPinDisconnected(args); + + internal void OnIsConnectingPinChanged(VisualScriptPin isConnectingPin) => Script.OnIsConnectingPinChanged(isConnectingPin); + + private void DisconnectAllPins() + { + foreach (VisualScriptPin pin in InputPins) + pin.DisconnectAll(); + + foreach (VisualScriptPin pin in OutputPins) + pin.DisconnectAll(); + + foreach (VisualScriptPinCollection pinCollection in InputPinCollections) + foreach (VisualScriptPin pin in pinCollection.Pins) + pin.DisconnectAll(); + + foreach (VisualScriptPinCollection pinCollection in OutputPinCollections) + foreach (VisualScriptPin pin in pinCollection.Pins) + pin.DisconnectAll(); + } + + private void Remove() + { + DisconnectAllPins(); + Script.RemoveNode(this); + } + + private bool RemoveCanExecute() => Node is not IExitNode; + + #endregion + } +} diff --git a/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptPin.cs b/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptPin.cs new file mode 100644 index 000000000..00b6e5184 --- /dev/null +++ b/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptPin.cs @@ -0,0 +1,112 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Windows; +using Artemis.Core.VisualScripting; +using Artemis.VisualScripting.Events; +using Artemis.VisualScripting.Internal; +using Artemis.VisualScripting.Model; +using Artemis.VisualScripting.ViewModel; + +namespace Artemis.VisualScripting.Editor.Controls.Wrapper +{ + public class VisualScriptPin : AbstractBindable + { + #region Constants + + private const double CABLE_OFFSET = 24 * 4; + + #endregion + + #region Properties & Fields + + private VisualScriptPin _isConnectingPin; + private VisualScriptCable _isConnectingCable; + + public VisualScriptNode Node { get; } + public IPin Pin { get; } + + public ObservableCollection Connections { get; } = new(); + + private Point _absolutePosition; + public Point AbsolutePosition + { + get => _absolutePosition; + internal set + { + if (SetProperty(ref _absolutePosition, value)) + OnPropertyChanged(nameof(AbsoluteCableTargetPosition)); + } + } + + public Point AbsoluteCableTargetPosition => Pin.Direction == PinDirection.Input ? new Point(AbsolutePosition.X - CABLE_OFFSET, AbsolutePosition.Y) : new Point(AbsolutePosition.X + CABLE_OFFSET, AbsolutePosition.Y); + + #endregion + + #region Constructors + + public VisualScriptPin(VisualScriptNode node, IPin pin) + { + this.Node = node; + this.Pin = pin; + } + + #endregion + + #region Methods + + public void SetConnecting(bool isConnecting) + { + if (isConnecting) + { + if (_isConnectingCable != null) + SetConnecting(false); + + _isConnectingPin = new VisualScriptPin(null, new IsConnectingPin(Pin.Direction == PinDirection.Input ? PinDirection.Output : PinDirection.Input)) { AbsolutePosition = AbsolutePosition }; + _isConnectingCable = new VisualScriptCable(this, _isConnectingPin); + Node.OnIsConnectingPinChanged(_isConnectingPin); + } + else + { + _isConnectingCable.Disconnect(); + _isConnectingCable = null; + _isConnectingPin = null; + Node.OnIsConnectingPinChanged(_isConnectingPin); + } + } + + internal void ConnectTo(VisualScriptCable cable) + { + if (Connections.Contains(cable)) return; + + if (Pin.Direction == PinDirection.Input) + { + List cables = Connections.ToList(); + foreach (VisualScriptCable c in cables) + c.Disconnect(); + } + + Connections.Add(cable); + Pin.ConnectTo(cable.GetConnectedPin(Pin)); + + Node?.OnPinConnected(new PinConnectedEventArgs(this, cable)); + } + + public void DisconnectAll() + { + List cables = Connections.ToList(); + foreach (VisualScriptCable cable in cables) + cable.Disconnect(); + } + + internal void Disconnect(VisualScriptCable cable) + { + Connections.Remove(cable); + Pin.DisconnectFrom(cable.GetConnectedPin(Pin)); + + Node?.OnPinDisconnected(new PinDisconnectedEventArgs(this, cable)); + } + + #endregion + } +} diff --git a/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptPinCollection.cs b/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptPinCollection.cs new file mode 100644 index 000000000..3e20fe8c5 --- /dev/null +++ b/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptPinCollection.cs @@ -0,0 +1,58 @@ +using System.Collections.ObjectModel; +using Artemis.Core.VisualScripting; +using Artemis.VisualScripting.ViewModel; + +namespace Artemis.VisualScripting.Editor.Controls.Wrapper +{ + public class VisualScriptPinCollection : AbstractBindable + { + #region Properties & Fields + + public VisualScriptNode Node { get; } + public IPinCollection PinCollection { get; } + + public ObservableCollection Pins { get; } = new(); + + #endregion + + #region Commands + + private ActionCommand _addPinCommand; + public ActionCommand AddPinCommand => _addPinCommand ??= new ActionCommand(AddPin); + + private ActionCommand _removePinCommand; + public ActionCommand RemovePinCommand => _removePinCommand ??= new ActionCommand(RemovePin); + + #endregion + + #region Constructors + + public VisualScriptPinCollection(VisualScriptNode node, IPinCollection pinCollection) + { + this.Node = node; + this.PinCollection = pinCollection; + + foreach (IPin pin in PinCollection) + Pins.Add(new VisualScriptPin(node, pin)); + } + + #endregion + + #region Methods + + public void AddPin() + { + IPin pin = PinCollection.AddPin(); + Pins.Add(new VisualScriptPin(Node, pin)); + } + + public void RemovePin(VisualScriptPin pin) + { + pin.DisconnectAll(); + PinCollection.Remove(pin.Pin); + Pins.Remove(pin); + } + + #endregion + } +} diff --git a/src/Artemis.VisualScripting/Editor/EditorStyles.xaml b/src/Artemis.VisualScripting/Editor/EditorStyles.xaml new file mode 100644 index 000000000..743c4dead --- /dev/null +++ b/src/Artemis.VisualScripting/Editor/EditorStyles.xaml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Editor/Styles/VisualScriptCablePresenter.xaml b/src/Artemis.VisualScripting/Editor/Styles/VisualScriptCablePresenter.xaml new file mode 100644 index 000000000..d51f0cfa3 --- /dev/null +++ b/src/Artemis.VisualScripting/Editor/Styles/VisualScriptCablePresenter.xaml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Controls/DataModelPicker.xaml.cs b/src/Artemis.UI.Shared/Controls/DataModelPicker.xaml.cs new file mode 100644 index 000000000..036031368 --- /dev/null +++ b/src/Artemis.UI.Shared/Controls/DataModelPicker.xaml.cs @@ -0,0 +1,303 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using Artemis.Core; +using Artemis.Core.Modules; +using Artemis.UI.Shared.Services; +using Stylet; + +namespace Artemis.UI.Shared.Controls +{ + /// + /// Interaction logic for DataModelPicker.xaml + /// + public partial class DataModelPicker : INotifyPropertyChanged + { + private static IDataModelUIService _dataModelUIService; + + /// + /// Gets or sets data model path + /// + public static readonly DependencyProperty DataModelPathProperty = DependencyProperty.Register( + nameof(DataModelPath), typeof(DataModelPath), typeof(DataModelPicker), + new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, DataModelPathPropertyChangedCallback) + ); + + /// + /// Gets or sets the brush to use when drawing the button + /// + public static readonly DependencyProperty PlaceholderProperty = DependencyProperty.Register( + nameof(Placeholder), typeof(string), typeof(DataModelPicker), + new FrameworkPropertyMetadata("Click to select") + ); + + /// + /// Gets or sets the brush to use when drawing the button + /// + public static readonly DependencyProperty ShowDataModelValuesProperty = DependencyProperty.Register(nameof(ShowDataModelValues), typeof(bool), typeof(DataModelPicker)); + + /// + /// Gets or sets the brush to use when drawing the button + /// + public static readonly DependencyProperty ShowFullPathProperty = DependencyProperty.Register(nameof(ShowFullPath), typeof(bool), typeof(DataModelPicker)); + + /// + /// Gets or sets the brush to use when drawing the button + /// + public static readonly DependencyProperty ButtonBrushProperty = DependencyProperty.Register(nameof(ButtonBrush), typeof(Brush), typeof(DataModelPicker)); + + /// + /// A list of extra modules to show data models of + /// + public static readonly DependencyProperty ModulesProperty = DependencyProperty.Register( + nameof(Modules), typeof(BindableCollection), typeof(DataModelPicker), + new FrameworkPropertyMetadata(new BindableCollection(), FrameworkPropertyMetadataOptions.None, ModulesPropertyChangedCallback) + ); + + /// + /// The data model view model to show, if not provided one will be retrieved by the control + /// + public static readonly DependencyProperty DataModelViewModelProperty = DependencyProperty.Register( + nameof(DataModelViewModel), typeof(DataModelPropertiesViewModel), typeof(DataModelPicker), + new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None, DataModelViewModelPropertyChangedCallback) + ); + + /// + /// A list of data model view models to show + /// + public static readonly DependencyProperty ExtraDataModelViewModelsProperty = DependencyProperty.Register( + nameof(ExtraDataModelViewModels), typeof(BindableCollection), typeof(DataModelPicker), + new FrameworkPropertyMetadata(new BindableCollection(), FrameworkPropertyMetadataOptions.None, ExtraDataModelViewModelsPropertyChangedCallback) + ); + + /// + /// A list of data model view models to show + /// + public static readonly DependencyProperty FilterTypesProperty = DependencyProperty.Register( + nameof(FilterTypes), typeof(BindableCollection), typeof(DataModelPicker), + new FrameworkPropertyMetadata(new BindableCollection()) + ); + + public DataModelPicker() + { + SelectPropertyCommand = new DelegateCommand(ExecuteSelectPropertyCommand); + + InitializeComponent(); + GetDataModel(); + UpdateValueDisplay(); + } + + /// + /// Gets or sets the brush to use when drawing the button + /// + public DataModelPath? DataModelPath + { + get => (DataModelPath) GetValue(DataModelPathProperty); + set => SetValue(DataModelPathProperty, value); + } + + /// + /// Gets or sets the brush to use when drawing the button + /// + public string Placeholder + { + get => (string) GetValue(PlaceholderProperty); + set => SetValue(PlaceholderProperty, value); + } + + /// + /// Gets or sets the brush to use when drawing the button + /// + public bool ShowDataModelValues + { + get => (bool) GetValue(ShowDataModelValuesProperty); + set => SetValue(ShowDataModelValuesProperty, value); + } + + /// + /// Gets or sets the brush to use when drawing the button + /// + public bool ShowFullPath + { + get => (bool) GetValue(ShowFullPathProperty); + set => SetValue(ShowFullPathProperty, value); + } + + /// + /// Gets or sets the brush to use when drawing the button + /// + public Brush ButtonBrush + { + get => (Brush) GetValue(ButtonBrushProperty); + set => SetValue(ButtonBrushProperty, value); + } + + /// + /// Gets or sets a list of extra modules to show data models of + /// + public BindableCollection? Modules + { + get => (BindableCollection) GetValue(ModulesProperty); + set => SetValue(ModulesProperty, value); + } + + /// + /// Gets or sets the data model view model to show + /// + public DataModelPropertiesViewModel? DataModelViewModel + { + get => (DataModelPropertiesViewModel) GetValue(DataModelViewModelProperty); + set => SetValue(DataModelViewModelProperty, value); + } + + /// + /// Gets or sets a list of data model view models to show + /// + public BindableCollection? ExtraDataModelViewModels + { + get => (BindableCollection) GetValue(ExtraDataModelViewModelsProperty); + set => SetValue(ExtraDataModelViewModelsProperty, value); + } + + /// + /// Gets or sets the types of properties this view model will allow to be selected + /// + public BindableCollection? FilterTypes + { + get => (BindableCollection) GetValue(FilterTypesProperty); + set => SetValue(FilterTypesProperty, value); + } + + /// + /// Command used by view + /// + public DelegateCommand SelectPropertyCommand { get; } + + internal static IDataModelUIService DataModelUIService + { + set + { + if (_dataModelUIService != null) + throw new AccessViolationException("This is not for you to touch"); + _dataModelUIService = value; + } + } + + /// + /// Occurs when a new path has been selected + /// + public event EventHandler? DataModelPathSelected; + + /// + /// Invokes the event + /// + /// + protected virtual void OnDataModelPathSelected(DataModelSelectedEventArgs e) + { + DataModelPathSelected?.Invoke(this, e); + } + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + private void GetDataModel() + { + ChangeDataModel(_dataModelUIService.GetPluginDataModelVisualization(Modules?.ToList() ?? new List(), true)); + } + + private void ChangeDataModel(DataModelPropertiesViewModel? dataModel) + { + if (DataModelViewModel != null) + { + DataModelViewModel.Dispose(); + DataModelViewModel.UpdateRequested -= DataModelOnUpdateRequested; + } + + DataModelViewModel = dataModel; + if (DataModelViewModel != null) + DataModelViewModel.UpdateRequested += DataModelOnUpdateRequested; + } + + private void UpdateValueDisplay() + { + ValueDisplay.Visibility = DataModelPath == null || DataModelPath.IsValid ? Visibility.Visible : Visibility.Collapsed; + ValuePlaceholder.Visibility = DataModelPath == null || DataModelPath.IsValid ? Visibility.Collapsed : Visibility.Visible; + + string? formattedPath = null; + if (DataModelPath != null && DataModelPath.IsValid) + formattedPath = string.Join(" › ", DataModelPath.Segments.Where(s => s.GetPropertyDescription() != null).Select(s => s.GetPropertyDescription()!.Name)); + + DataModelButton.ToolTip = formattedPath; + ValueDisplayTextBlock.Text = ShowFullPath + ? formattedPath + : DataModelPath?.Segments.LastOrDefault()?.GetPropertyDescription()?.Name ?? DataModelPath?.Segments.LastOrDefault()?.Identifier; + } + + private void DataModelOnUpdateRequested(object? sender, EventArgs e) + { + DataModelViewModel?.ApplyTypeFilter(true, FilterTypes?.ToArray() ?? Type.EmptyTypes); + if (ExtraDataModelViewModels == null) return; + foreach (DataModelPropertiesViewModel extraDataModelViewModel in ExtraDataModelViewModels) + extraDataModelViewModel.ApplyTypeFilter(true, FilterTypes?.ToArray() ?? Type.EmptyTypes); + } + + private void ExecuteSelectPropertyCommand(object? context) + { + if (context is not DataModelVisualizationViewModel selected) + return; + + DataModelPath = selected.DataModelPath; + OnDataModelPathSelected(new DataModelSelectedEventArgs(DataModelPath)); + } + + private void PropertyButton_OnClick(object sender, RoutedEventArgs e) + { + // DataContext is not set when using left button, I don't know why but there it is + if (sender is Button button && button.ContextMenu != null) + { + button.ContextMenu.DataContext = button.DataContext; + button.ContextMenu.IsOpen = true; + } + } + + private static void DataModelPathPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is not DataModelPicker dataModelPicker) + return; + + if (e.OldValue is DataModelPath oldPath) + oldPath.Dispose(); + + dataModelPicker.UpdateValueDisplay(); + } + + private static void ModulesPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is not DataModelPicker dataModelPicker) + return; + + dataModelPicker.GetDataModel(); + } + + private static void DataModelViewModelPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is not DataModelPicker dataModelPicker) + return; + } + + private static void ExtraDataModelViewModelsPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is not DataModelPicker dataModelPicker) + return; + } + + public event PropertyChangedEventHandler? PropertyChanged; + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Converters/IntToVisibilityConverter.cs b/src/Artemis.UI.Shared/Converters/IntToVisibilityConverter.cs new file mode 100644 index 000000000..2db2cd50a --- /dev/null +++ b/src/Artemis.UI.Shared/Converters/IntToVisibilityConverter.cs @@ -0,0 +1,39 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace Artemis.UI.Shared +{ + /// + /// Converts to + /// + public class IntToVisibilityConverter : IValueConverter + { + /// + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + Parameters direction; + if (parameter == null) + direction = Parameters.Normal; + else + direction = (Parameters) Enum.Parse(typeof(Parameters), (string) parameter); + + return direction == Parameters.Normal + ? value is > 1 ? Visibility.Visible : Visibility.Collapsed + : value is > 1 ? Visibility.Collapsed : Visibility.Visible; + } + + /// + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + + private enum Parameters + { + Normal, + Inverted + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicView.xaml b/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicView.xaml deleted file mode 100644 index 6ebda995f..000000000 --- a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicView.xaml +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicView.xaml.cs b/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicView.xaml.cs deleted file mode 100644 index a487bdfea..000000000 --- a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicView.xaml.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Windows; -using System.Windows.Controls; - -namespace Artemis.UI.Shared.Input -{ - /// - /// Interaction logic for DataModelDynamicView.xaml - /// - public partial class DataModelDynamicView : UserControl - { - /// - /// Creates a new instance of the class - /// - public DataModelDynamicView() - { - InitializeComponent(); - } - - private void PropertyButton_OnClick(object sender, RoutedEventArgs e) - { - // DataContext is not set when using left button, I don't know why but there it is - if (sender is Button button && button.ContextMenu != null) - { - button.ContextMenu.DataContext = button.DataContext; - button.ContextMenu.IsOpen = true; - } - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicViewModel.cs deleted file mode 100644 index ad9b65584..000000000 --- a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicViewModel.cs +++ /dev/null @@ -1,387 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Linq; -using System.Timers; -using System.Windows.Media; -using Artemis.Core; -using Artemis.Core.Modules; -using Artemis.Core.Services; -using Artemis.UI.Shared.Services; -using MaterialDesignColors.ColorManipulation; -using Stylet; - -namespace Artemis.UI.Shared.Input -{ - /// - /// Represents a view model that allows selecting a data model property used by boolean operations on a certain data - /// model property - /// - public class DataModelDynamicViewModel : PropertyChangedBase, IDisposable - { - private readonly List _modules; - private readonly IDataModelUIService _dataModelUIService; - private readonly Timer _updateTimer; - private SolidColorBrush _buttonBrush = new(Color.FromRgb(171, 71, 188)); - private DataModelPath? _dataModelPath; - private DataModelPropertiesViewModel? _dataModelViewModel; - private bool _displaySwitchButton; - private Type[] _filterTypes = Array.Empty(); - private bool _isDataModelViewModelOpen; - private bool _isEnabled = true; - private string _placeholder = "Select a property"; - private readonly PluginSetting _showFullPath; - - internal DataModelDynamicViewModel(List modules, ISettingsService settingsService, IDataModelUIService dataModelUIService) - { - _modules = modules; - _dataModelUIService = dataModelUIService; - _updateTimer = new Timer(500); - _showFullPath = settingsService.GetSetting("ProfileEditor.ShowFullPaths", true); - - ExtraDataModelViewModels = new BindableCollection(); - ShowDataModelValues = settingsService.GetSetting("ProfileEditor.ShowDataModelValues"); - SelectPropertyCommand = new DelegateCommand(ExecuteSelectPropertyCommand); - - Initialize(); - } - - /// - /// Gets or sets the brush to use for the input button - /// - public SolidColorBrush ButtonBrush - { - get => _buttonBrush; - set - { - if (!SetAndNotify(ref _buttonBrush, value)) return; - NotifyOfPropertyChange(nameof(SwitchButtonBrush)); - } - } - - /// - /// Gets the brush to use for the switch button - /// - public SolidColorBrush SwitchButtonBrush => new(ButtonBrush.Color.Darken()); - - /// - /// Gets or sets the placeholder text when no value is entered - /// - public string Placeholder - { - get => _placeholder; - set => SetAndNotify(ref _placeholder, value); - } - - /// - /// Gets or sets the enabled state of the input - /// - public bool IsEnabled - { - get => _isEnabled; - set => SetAndNotify(ref _isEnabled, value); - } - - /// - /// Gets or sets whether the switch button should be displayed - /// - public bool DisplaySwitchButton - { - get => _displaySwitchButton; - set => SetAndNotify(ref _displaySwitchButton, value); - } - - /// - /// Gets or sets the types of properties this view model will allow to be selected - /// - public Type[] FilterTypes - { - get => _filterTypes; - set - { - if (!SetAndNotify(ref _filterTypes, value)) return; - DataModelViewModel?.ApplyTypeFilter(true, FilterTypes); - } - } - - /// - /// Gets a bindable collection of extra data model view models to show - /// - public BindableCollection ExtraDataModelViewModels { get; } - - /// - /// Gets a boolean indicating whether there are any modules providing data models - /// - public bool HasNoModules => (DataModelViewModel == null || !DataModelViewModel.Children.Any()) && !HasExtraDataModels; - - /// - /// Gets a boolean indicating whether there are any extra data models - /// - public bool HasExtraDataModels => ExtraDataModelViewModels.Any(); - - /// - /// Command used by view - /// - public DelegateCommand SelectPropertyCommand { get; } - - /// - /// Setting used by view - /// - public PluginSetting ShowDataModelValues { get; } - - public PluginSetting ShowFullPath { get; } - - /// - /// Gets or sets root the data model view model - /// - public DataModelPropertiesViewModel? DataModelViewModel - { - get => _dataModelViewModel; - private set => SetAndNotify(ref _dataModelViewModel, value); - } - - /// - /// Gets or sets a boolean indicating whether the data model is open - /// - public bool IsDataModelViewModelOpen - { - get => _isDataModelViewModelOpen; - set - { - if (!SetAndNotify(ref _isDataModelViewModelOpen, value)) return; - if (value) - { - UpdateDataModelVisualization(); - if (DataModelViewModel != null) - OpenSelectedValue(DataModelViewModel); - } - } - } - - /// - /// Gets or sets the data model path of the currently selected data model property - /// - public DataModelPath? DataModelPath - { - get => _dataModelPath; - private set - { - if (!SetAndNotify(ref _dataModelPath, value)) return; - NotifyOfPropertyChange(nameof(IsValid)); - NotifyOfPropertyChange(nameof(DisplayValue)); - NotifyOfPropertyChange(nameof(DisplayPath)); - } - } - - /// - /// Gets a boolean indicating whether the current selection is valid - /// - public bool IsValid => DataModelPath?.IsValid ?? true; - - /// - /// Gets the display name of the currently selected property - /// - public string? DisplayValue - { - get - { - if (_showFullPath.Value) - return DisplayPath; - return DataModelPath?.GetPropertyDescription()?.Name ?? DataModelPath?.Segments.LastOrDefault()?.Identifier; - } - } - - /// - /// Gets the human readable path of the currently selected property - /// - public string DisplayPath - { - get - { - if (DataModelPath == null) - return "Click to select a property"; - if (!DataModelPath.IsValid) - return "Invalid path"; - - return string.Join(" › ", DataModelPath.Segments.Where(s => s.GetPropertyDescription()!= null).Select(s => s.GetPropertyDescription()!.Name)); - } - } - - /// - /// Gets or sets a boolean indicating whether event child VMs should be loaded, defaults to - /// - public bool LoadEventChildren { get; set; } = true; - - /// - /// Changes the root data model VM stored in to the provided - /// - /// - /// The data model VM to set the new root data model to - public void ChangeDataModel(DataModelPropertiesViewModel dataModel) - { - if (DataModelViewModel != null) - DataModelViewModel.UpdateRequested -= DataModelOnUpdateRequested; - - DataModelViewModel = dataModel; - - if (DataModelViewModel != null) - DataModelViewModel.UpdateRequested += DataModelOnUpdateRequested; - } - - /// - /// Changes the currently selected property by its path - /// - /// The path of the property to set the selection to - public void ChangeDataModelPath(DataModelPath? dataModelPath) - { - DataModelPath?.Dispose(); - DataModelPath = dataModelPath != null ? new DataModelPath(dataModelPath) : null; - } - - /// - /// Requests switching the input type to static using a - /// - public void SwitchToStatic() - { - ChangeDataModelPath(null); - OnPropertySelected(new DataModelInputDynamicEventArgs(null)); - OnSwitchToStaticRequested(); - } - - private void Initialize() - { - // Get the data models - DataModelViewModel = _dataModelUIService.GetPluginDataModelVisualization(_modules, true); - if (DataModelViewModel != null) - DataModelViewModel.UpdateRequested += DataModelOnUpdateRequested; - ExtraDataModelViewModels.CollectionChanged += ExtraDataModelViewModelsOnCollectionChanged; - _updateTimer.Start(); - _updateTimer.Elapsed += OnUpdateTimerOnElapsed; - _showFullPath.SettingChanged += ShowFullPathOnSettingChanged; - } - - - private void ExecuteSelectPropertyCommand(object? context) - { - if (context is not DataModelVisualizationViewModel selected) - return; - - ChangeDataModelPath(selected.DataModelPath); - OnPropertySelected(new DataModelInputDynamicEventArgs(DataModelPath)); - } - - #region IDisposable - - /// - /// Releases the unmanaged resources used by the object and optionally releases the managed resources. - /// - /// - /// to release both managed and unmanaged resources; - /// to release only unmanaged resources. - /// - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _updateTimer.Stop(); - _updateTimer.Dispose(); - _updateTimer.Elapsed -= OnUpdateTimerOnElapsed; - _showFullPath.SettingChanged -= ShowFullPathOnSettingChanged; - - DataModelViewModel?.Dispose(); - DataModelPath?.Dispose(); - } - } - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - #endregion - - #region Event handlers - - private void DataModelOnUpdateRequested(object? sender, EventArgs e) - { - DataModelViewModel?.ApplyTypeFilter(true, FilterTypes); - foreach (DataModelPropertiesViewModel extraDataModelViewModel in ExtraDataModelViewModels) - extraDataModelViewModel.ApplyTypeFilter(true, FilterTypes); - } - - private void ExtraDataModelViewModelsOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) - { - NotifyOfPropertyChange(nameof(HasExtraDataModels)); - } - - private void OnUpdateTimerOnElapsed(object? sender, ElapsedEventArgs e) - { - if (!IsDataModelViewModelOpen) - return; - - UpdateDataModelVisualization(); - } - - private void UpdateDataModelVisualization() - { - DataModelViewModel?.Update(_dataModelUIService, new DataModelUpdateConfiguration(LoadEventChildren)); - foreach (DataModelPropertiesViewModel extraDataModelViewModel in ExtraDataModelViewModels) - extraDataModelViewModel.Update(_dataModelUIService, new DataModelUpdateConfiguration(LoadEventChildren)); - } - - private void OpenSelectedValue(DataModelVisualizationViewModel dataModelPropertiesViewModel) - { - if (DataModelPath == null) - return; - - if (dataModelPropertiesViewModel.Children.Any(c => c.DataModelPath != null && DataModelPath.Path.StartsWith(c.DataModelPath.Path))) - { - dataModelPropertiesViewModel.IsVisualizationExpanded = true; - foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in dataModelPropertiesViewModel.Children) - { - OpenSelectedValue(dataModelVisualizationViewModel); - } - } - } - - private void ShowFullPathOnSettingChanged(object? sender, EventArgs e) - { - NotifyOfPropertyChange(nameof(DisplayValue)); - } - - #endregion - - #region Events - - /// - /// Occurs when anew property has been selected - /// - public event EventHandler? PropertySelected; - - /// - /// Occurs when a switch to static input has been requested - /// - public event EventHandler? SwitchToStaticRequested; - - /// - /// Invokes the event - /// - /// - protected virtual void OnPropertySelected(DataModelInputDynamicEventArgs e) - { - PropertySelected?.Invoke(this, e); - } - - /// - /// Invokes the event - /// - protected virtual void OnSwitchToStaticRequested() - { - SwitchToStaticRequested?.Invoke(this, EventArgs.Empty); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelStaticView.xaml b/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelStaticView.xaml deleted file mode 100644 index a069d383a..000000000 --- a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelStaticView.xaml +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelStaticViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelStaticViewModel.cs deleted file mode 100644 index 2ef06ec9a..000000000 --- a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelStaticViewModel.cs +++ /dev/null @@ -1,285 +0,0 @@ -using System; -using System.Linq; -using System.Windows; -using System.Windows.Input; -using System.Windows.Media; -using Artemis.Core; -using Artemis.Core.Modules; -using Artemis.UI.Shared.Services; -using MaterialDesignColors.ColorManipulation; -using Stylet; - -namespace Artemis.UI.Shared.Input -{ - /// - /// Represents a view model that allows inputting a static value used by boolean operations on a certain data model - /// property - /// - public class DataModelStaticViewModel : PropertyChangedBase, IDisposable - { - private readonly IDataModelUIService _dataModelUIService; - private readonly Window? _rootView; - private SolidColorBrush _buttonBrush = new(Color.FromRgb(171, 71, 188)); - private bool _displaySwitchButton; - private DataModelDisplayViewModel? _displayViewModel; - private DataModelInputViewModel? _inputViewModel; - private bool _isEnabled; - private string _placeholder = "Enter a value"; - private DataModelPropertyAttribute _targetDescription; - private Type _targetType; - private object? _value; - - internal DataModelStaticViewModel(Type targetType, DataModelPropertyAttribute targetDescription, IDataModelUIService dataModelUIService) - { - _dataModelUIService = dataModelUIService; - _rootView = Application.Current.Windows.OfType().SingleOrDefault(x => x.IsActive); - - _targetType = targetType; - _targetDescription = targetDescription; - _isEnabled = TargetType != null; - _displayViewModel = _dataModelUIService.GetDataModelDisplayViewModel(TargetType ?? typeof(object), TargetDescription, true); - - if (_rootView != null) - { - _rootView.MouseUp += RootViewOnMouseUp; - _rootView.KeyUp += RootViewOnKeyUp; - } - } - - /// - /// Gets or sets the brush to use for the input button - /// - public SolidColorBrush ButtonBrush - { - get => _buttonBrush; - set - { - if (!SetAndNotify(ref _buttonBrush, value)) return; - NotifyOfPropertyChange(nameof(SwitchButtonBrush)); - } - } - - /// - /// Gets the brush to use for the switch button - /// - public SolidColorBrush SwitchButtonBrush => new(ButtonBrush.Color.Darken()); - - /// - /// Gets the view model used to display the value - /// - public DataModelDisplayViewModel? DisplayViewModel - { - get => _displayViewModel; - private set => SetAndNotify(ref _displayViewModel, value); - } - - /// - /// Gets the view model used to edit the value - /// - public DataModelInputViewModel? InputViewModel - { - get => _inputViewModel; - private set => SetAndNotify(ref _inputViewModel, value); - } - - /// - /// Gets the type of the target property - /// - public Type TargetType - { - get => _targetType; - private set => SetAndNotify(ref _targetType, value); - } - - /// - /// Gets the description of the target property - /// - public DataModelPropertyAttribute TargetDescription - { - get => _targetDescription; - set => SetAndNotify(ref _targetDescription, value); - } - - /// - /// Gets or sets the value of the target - /// - public object? Value - { - get => _value; - set - { - if (!SetAndNotify(ref _value, value)) return; - DisplayViewModel?.UpdateValue(_value); - } - } - - /// - /// Gets or sets the placeholder text when no value is entered - /// - public string Placeholder - { - get => _placeholder; - set => SetAndNotify(ref _placeholder, value); - } - - /// - /// Gets or sets the enabled state of the input - /// - public bool IsEnabled - { - get => _isEnabled; - private set => SetAndNotify(ref _isEnabled, value); - } - - /// - /// Gets or sets whether the switch button should be displayed - /// - public bool DisplaySwitchButton - { - get => _displaySwitchButton; - set => SetAndNotify(ref _displaySwitchButton, value); - } - - /// - /// Activates the input view model - /// - public void ActivateInputViewModel() - { - InputViewModel = _dataModelUIService.GetDataModelInputViewModel( - TargetType, - TargetDescription, - Value, - ApplyFreeInput - ); - } - - /// - /// Updates the target type - /// - /// The new target type - public void UpdateTargetType(Type target) - { - TargetType = target; - DisplayViewModel = _dataModelUIService.GetDataModelDisplayViewModel(TargetType ?? typeof(object), TargetDescription, true); - IsEnabled = TargetType != null; - - // If null, clear the input - if (TargetType == null) - { - ApplyFreeInput(null, true); - return; - } - - // If the type changed, reset to the default type - if (Value == null || !TargetType.IsCastableFrom(Value.GetType())) - // Force the VM to close if it was open and apply the new value - ApplyFreeInput(TargetType.GetDefault(), true); - } - - /// - /// Requests switching the input type to dynamic using a - /// - public void SwitchToDynamic() - { - InputViewModel?.Cancel(); - ApplyFreeInput(TargetType.GetDefault(), true); - - OnSwitchToDynamicRequested(); - } - - private void ApplyFreeInput(object? value, bool submitted) - { - if (submitted) - OnValueUpdated(new DataModelInputStaticEventArgs(value)); - - InputViewModel = null; - Value = value; - } - - #region IDisposable - - /// - /// Releases the unmanaged resources used by the object and optionally releases the managed resources. - /// - /// - /// to release both managed and unmanaged resources; - /// to release only unmanaged resources. - /// - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - if (_rootView != null) - { - _rootView.MouseUp -= RootViewOnMouseUp; - _rootView.KeyUp -= RootViewOnKeyUp; - } - } - } - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - #endregion - - - #region Event handlers - - private void RootViewOnKeyUp(object sender, KeyEventArgs e) - { - if (InputViewModel == null) - return; - - if (e.Key == Key.Escape) - InputViewModel.Cancel(); - else if (e.Key == Key.Enter) - InputViewModel.Submit(); - } - - private void RootViewOnMouseUp(object sender, MouseButtonEventArgs e) - { - if (InputViewModel == null) - return; - - if (sender is FrameworkElement frameworkElement && !frameworkElement.IsDescendantOf(InputViewModel.View)) - InputViewModel.Submit(); - } - - #endregion - - #region Events - - /// - /// Occurs when the value of the property has been updated - /// - public event EventHandler? ValueUpdated; - - /// - /// Occurs when a switch to dynamic input has been requested - /// - public event EventHandler? SwitchToDynamicRequested; - - /// - /// Invokes the event - /// - /// - protected virtual void OnValueUpdated(DataModelInputStaticEventArgs e) - { - ValueUpdated?.Invoke(this, e); - } - - /// - /// Invokes the event - /// - protected virtual void OnSwitchToDynamicRequested() - { - SwitchToDynamicRequested?.Invoke(this, EventArgs.Empty); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertiesViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertiesViewModel.cs index bbc85258a..d9060f943 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertiesViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertiesViewModel.cs @@ -11,15 +11,12 @@ namespace Artemis.UI.Shared /// public class DataModelListPropertiesViewModel : DataModelPropertiesViewModel { - private readonly ListPredicateWrapperDataModel _listPredicateWrapper; private object? _displayValue; private int _index; private Type? _listType; internal DataModelListPropertiesViewModel(Type listType, string? name) : base(null, null, null) { - _listPredicateWrapper = ListPredicateWrapperDataModel.Create(listType, name); - DataModel = _listPredicateWrapper; ListType = listType; } @@ -61,8 +58,6 @@ namespace Artemis.UI.Shared /// public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration) { - _listPredicateWrapper.UntypedValue = DisplayValue; - PopulateProperties(dataModelUIService, configuration); if (DisplayViewModel == null) return; diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertyViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertyViewModel.cs index 2e3c7fe32..29bdeb259 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertyViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertyViewModel.cs @@ -10,22 +10,17 @@ namespace Artemis.UI.Shared /// public class DataModelListPropertyViewModel : DataModelPropertyViewModel { - private readonly ListPredicateWrapperDataModel _listPredicateWrapper; private int _index; private Type? _listType; internal DataModelListPropertyViewModel(Type listType, DataModelDisplayViewModel displayViewModel, string? name) : base(null, null, null) { - _listPredicateWrapper = ListPredicateWrapperDataModel.Create(listType, name); - DataModel = _listPredicateWrapper; ListType = listType; DisplayViewModel = displayViewModel; } internal DataModelListPropertyViewModel(Type listType, string? name) : base(null, null, null) { - _listPredicateWrapper = ListPredicateWrapperDataModel.Create(listType, name); - DataModel = _listPredicateWrapper; ListType = listType; } @@ -60,8 +55,6 @@ namespace Artemis.UI.Shared if (DisplayValue == null) return; - _listPredicateWrapper.UntypedValue = DisplayValue; - if (DisplayViewModel == null) DisplayViewModel = dataModelUIService.GetDataModelDisplayViewModel(DisplayValue.GetType(), PropertyDescription, true); diff --git a/src/Artemis.UI.Shared/Events/DataModelInputStaticEventArgs.cs b/src/Artemis.UI.Shared/Events/DataModelInputStaticEventArgs.cs deleted file mode 100644 index 650b558b8..000000000 --- a/src/Artemis.UI.Shared/Events/DataModelInputStaticEventArgs.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using Artemis.UI.Shared.Input; - -namespace Artemis.UI.Shared -{ - /// - /// Provides data about submit events raised by - /// - public class DataModelInputStaticEventArgs : EventArgs - { - internal DataModelInputStaticEventArgs(object? value) - { - Value = value; - } - - /// - /// The value that was submitted - /// - public object? Value { get; } - } -} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Events/DataModelInputDynamicEventArgs.cs b/src/Artemis.UI.Shared/Events/DataModelSelectedEventArgs.cs similarity index 50% rename from src/Artemis.UI.Shared/Events/DataModelInputDynamicEventArgs.cs rename to src/Artemis.UI.Shared/Events/DataModelSelectedEventArgs.cs index e5a9806de..9306237ed 100644 --- a/src/Artemis.UI.Shared/Events/DataModelInputDynamicEventArgs.cs +++ b/src/Artemis.UI.Shared/Events/DataModelSelectedEventArgs.cs @@ -1,22 +1,22 @@ using System; using Artemis.Core; -using Artemis.UI.Shared.Input; +using Artemis.UI.Shared.Controls; namespace Artemis.UI.Shared { /// - /// Provides data about selection events raised by + /// Provides data about selection events raised by /// - public class DataModelInputDynamicEventArgs : EventArgs + public class DataModelSelectedEventArgs : EventArgs { - internal DataModelInputDynamicEventArgs(DataModelPath? dataModelPath) - { - DataModelPath = dataModelPath; - } - /// /// Gets the data model path that was selected /// - public DataModelPath? DataModelPath { get; } + public DataModelPath? Path { get; } + + internal DataModelSelectedEventArgs(DataModelPath? path) + { + Path = path; + } } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Extensions/DataModelWrapperExtensions.cs b/src/Artemis.UI.Shared/Extensions/DataModelWrapperExtensions.cs deleted file mode 100644 index d21024543..000000000 --- a/src/Artemis.UI.Shared/Extensions/DataModelWrapperExtensions.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Linq; -using Artemis.Core; -using Artemis.UI.Shared.Services; - -namespace Artemis.UI.Shared -{ - /// - /// Provides extensions for special data model wrappers used by events and list conditions - /// - public static class DataModelWrapperExtensions - { - /// - /// Creates a view model for a - /// - /// The wrapper to create the view model for - /// The data model UI service to be used by the view model - /// The update configuration to be used by the view model - /// The created view model - public static DataModelPropertiesViewModel CreateViewModel(this EventPredicateWrapperDataModel wrapper, IDataModelUIService dataModelUIService, DataModelUpdateConfiguration configuration) - { - DataModelPropertiesViewModel viewModel = new(wrapper, null, new DataModelPath(wrapper)); - viewModel.Update(dataModelUIService, configuration); - viewModel.UpdateRequested += (sender, args) => viewModel.Update(dataModelUIService, configuration); - viewModel.Children.First().IsVisualizationExpanded = true; - - return viewModel; - } - - /// - /// Creates a view model for a - /// - /// The wrapper to create the view model for - /// The data model UI service to be used by the view model - /// The update configuration to be used by the view model - /// The created view model - public static DataModelPropertiesViewModel CreateViewModel(this ListPredicateWrapperDataModel wrapper, IDataModelUIService dataModelUIService, DataModelUpdateConfiguration configuration) - { - DataModelPropertiesViewModel viewModel = new(wrapper, null, new DataModelPath(wrapper)); - viewModel.Update(dataModelUIService, configuration); - viewModel.UpdateRequested += (sender, args) => viewModel.Update(dataModelUIService, configuration); - viewModel.Children.First().IsVisualizationExpanded = true; - - return viewModel; - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Ninject/Factories/ISharedVMFactory.cs b/src/Artemis.UI.Shared/Ninject/Factories/ISharedVMFactory.cs index 21cce1e60..228b34c6e 100644 --- a/src/Artemis.UI.Shared/Ninject/Factories/ISharedVMFactory.cs +++ b/src/Artemis.UI.Shared/Ninject/Factories/ISharedVMFactory.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using Artemis.Core.Modules; -using Artemis.UI.Shared.Input; - -namespace Artemis.UI.Shared +namespace Artemis.UI.Shared { /// /// Represents a factory for view models provided by the Artemis Shared UI library @@ -11,25 +6,4 @@ namespace Artemis.UI.Shared public interface ISharedVmFactory { } - - /// - /// A factory that allows the creation of data model view models - /// - public interface IDataModelVmFactory : ISharedVmFactory - { - /// - /// Creates a new instance of the class - /// - /// The modules to associate the dynamic view model with - /// A new instance of the class - DataModelDynamicViewModel DataModelDynamicViewModel(List modules); - - /// - /// Creates a new instance of the class - /// - /// The type of property that is expected in this input - /// The description of the property that this input is for - /// A new instance of the class - DataModelStaticViewModel DataModelStaticViewModel(Type targetType, DataModelPropertyAttribute targetDescription); - } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/DataModelUIService.cs b/src/Artemis.UI.Shared/Services/DataModelUIService.cs index dc8e5ca7e..db4c0d8bd 100644 --- a/src/Artemis.UI.Shared/Services/DataModelUIService.cs +++ b/src/Artemis.UI.Shared/Services/DataModelUIService.cs @@ -5,7 +5,6 @@ using Artemis.Core; using Artemis.Core.Modules; using Artemis.Core.Services; using Artemis.UI.Shared.DefaultTypes.DataModel.Display; -using Artemis.UI.Shared.Input; using Ninject; using Ninject.Parameters; @@ -14,15 +13,13 @@ namespace Artemis.UI.Shared.Services internal class DataModelUIService : IDataModelUIService { private readonly IDataModelService _dataModelService; - private readonly IDataModelVmFactory _dataModelVmFactory; private readonly IKernel _kernel; private readonly List _registeredDataModelDisplays; private readonly List _registeredDataModelEditors; - public DataModelUIService(IDataModelService dataModelService, IDataModelVmFactory dataModelVmFactory, IKernel kernel) + public DataModelUIService(IDataModelService dataModelService, IKernel kernel) { _dataModelService = dataModelService; - _dataModelVmFactory = dataModelVmFactory; _kernel = kernel; _registeredDataModelEditors = new List(); _registeredDataModelDisplays = new List(); @@ -223,22 +220,7 @@ namespace Artemis.UI.Shared.Services return null; } } - - public DataModelDynamicViewModel GetDynamicSelectionViewModel(Module? module) - { - return _dataModelVmFactory.DataModelDynamicViewModel(module == null ? new List() : new List {module}); - } - - public DataModelDynamicViewModel GetDynamicSelectionViewModel(List modules) - { - return _dataModelVmFactory.DataModelDynamicViewModel(modules); - } - - public DataModelStaticViewModel GetStaticInputViewModel(Type targetType, DataModelPropertyAttribute targetDescription) - { - return _dataModelVmFactory.DataModelStaticViewModel(targetType, targetDescription); - } - + private DataModelInputViewModel InstantiateDataModelInputViewModel(DataModelVisualizationRegistration registration, DataModelPropertyAttribute? description, object? initialValue) { // This assumes the type can be converted, that has been checked when the VM was created diff --git a/src/Artemis.UI.Shared/Services/Interfaces/IDataModelUIService.cs b/src/Artemis.UI.Shared/Services/Interfaces/IDataModelUIService.cs index 2c3f6efdb..962da118d 100644 --- a/src/Artemis.UI.Shared/Services/Interfaces/IDataModelUIService.cs +++ b/src/Artemis.UI.Shared/Services/Interfaces/IDataModelUIService.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using Artemis.Core; using Artemis.Core.Modules; -using Artemis.UI.Shared.Input; namespace Artemis.UI.Shared.Services { @@ -103,27 +102,5 @@ namespace Artemis.UI.Shared.Services /// A function to call whenever the input was updated (submitted or not) /// The most appropriate input view model for the provided DataModelInputViewModel? GetDataModelInputViewModel(Type propertyType, DataModelPropertyAttribute? description, object? initialValue, Action updateCallback); - - /// - /// Creates a view model that allows selecting a value from the data model - /// - /// An extra non-always active module to include - /// - DataModelDynamicViewModel GetDynamicSelectionViewModel(Module? module); - - /// - /// Creates a view model that allows selecting a value from the data model - /// - /// A list of extra extra non-always active modules to include - /// A view model that allows selecting a value from the data model - DataModelDynamicViewModel GetDynamicSelectionViewModel(List modules); - - /// - /// Creates a view model that allows entering a value matching the target data model type - /// - /// The type of data model property to allow input for - /// The description of the target data model property - /// A view model that allows entering a value matching the target data model type - DataModelStaticViewModel GetStaticInputViewModel(Type targetType, DataModelPropertyAttribute targetDescription); } } \ No newline at end of file diff --git a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs index 375efa7f4..7d1c85aa3 100644 --- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -1,15 +1,10 @@ -using System.Collections.Generic; -using Artemis.Core; +using Artemis.Core; using Artemis.Core.Modules; using Artemis.Core.ScriptingProviders; using Artemis.UI.Screens.Header; using Artemis.UI.Screens.Plugins; -using Artemis.UI.Screens.ProfileEditor.Conditions; -using Artemis.UI.Screens.ProfileEditor.DisplayConditions; using Artemis.UI.Screens.ProfileEditor.LayerProperties; using Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings; -using Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.ConditionalDataBinding; -using Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDataBinding; using Artemis.UI.Screens.ProfileEditor.LayerProperties.LayerEffects; using Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline; using Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree; @@ -83,16 +78,6 @@ namespace Artemis.UI.Ninject.Factories SelectionRemoveToolViewModel SelectionRemoveToolViewModel(PanZoomViewModel panZoomViewModel); } - public interface IDataModelConditionsVmFactory : IVmFactory - { - DataModelConditionGroupViewModel DataModelConditionGroupViewModel(DataModelConditionGroup dataModelConditionGroup, ConditionGroupType groupType, List modules); - DataModelConditionListViewModel DataModelConditionListViewModel(DataModelConditionList dataModelConditionList, List modules); - DataModelConditionEventViewModel DataModelConditionEventViewModel(DataModelConditionEvent dataModelConditionEvent, List modules); - DataModelConditionGeneralPredicateViewModel DataModelConditionGeneralPredicateViewModel(DataModelConditionGeneralPredicate dataModelConditionGeneralPredicate, List modules); - DataModelConditionListPredicateViewModel DataModelConditionListPredicateViewModel(DataModelConditionListPredicate dataModelConditionListPredicate, List modules); - DataModelConditionEventPredicateViewModel DataModelConditionEventPredicateViewModel(DataModelConditionEventPredicate dataModelConditionEventPredicate, List modules); - } - public interface ILayerPropertyVmFactory : IVmFactory { LayerPropertyViewModel LayerPropertyViewModel(ILayerProperty layerProperty); @@ -131,17 +116,9 @@ namespace Artemis.UI.Ninject.Factories NodeScriptWindowViewModel NodeScriptWindowViewModel(NodeScript nodeScript); } - // TODO: Move these two public interface IDataBindingsVmFactory { IDataBindingViewModel DataBindingViewModel(IDataBindingRegistration registration); - DirectDataBindingModeViewModel DirectDataBindingModeViewModel(DirectDataBinding directDataBinding); - DataBindingModifierViewModel DataBindingModifierViewModel(DataBindingModifier modifier); - - ConditionalDataBindingModeViewModel ConditionalDataBindingModeViewModel( - ConditionalDataBinding conditionalDataBinding); - - DataBindingConditionViewModel DataBindingConditionViewModel(DataBindingCondition dataBindingCondition); } public interface IPropertyVmFactory diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Abstract/DataModelConditionPredicateViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Abstract/DataModelConditionPredicateViewModel.cs deleted file mode 100644 index 9995cd02f..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Abstract/DataModelConditionPredicateViewModel.cs +++ /dev/null @@ -1,337 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Windows; -using System.Windows.Media; -using Artemis.Core; -using Artemis.Core.Modules; -using Artemis.Core.Services; -using Artemis.UI.Shared; -using Artemis.UI.Shared.Input; -using Artemis.UI.Shared.Services; -using Stylet; - -namespace Artemis.UI.Screens.ProfileEditor.Conditions.Abstract -{ - public abstract class DataModelConditionPredicateViewModel : DataModelConditionViewModel, IDisposable - { - private readonly IConditionOperatorService _conditionOperatorService; - private readonly IDataModelUIService _dataModelUIService; - private readonly List _modules; - private readonly IProfileEditorService _profileEditorService; - private DataModelStaticViewModel _rightSideInputViewModel; - private DataModelDynamicViewModel _rightSideSelectionViewModel; - private BaseConditionOperator _selectedOperator; - - private List _supportedInputTypes; - - protected DataModelConditionPredicateViewModel( - DataModelConditionPredicate dataModelConditionPredicate, - List modules, - IProfileEditorService profileEditorService, - IDataModelUIService dataModelUIService, - IConditionOperatorService conditionOperatorService, - ISettingsService settingsService) : base(dataModelConditionPredicate) - { - _modules = modules; - _profileEditorService = profileEditorService; - _dataModelUIService = dataModelUIService; - _conditionOperatorService = conditionOperatorService; - _supportedInputTypes = new List(); - - SelectOperatorCommand = new DelegateCommand(ExecuteSelectOperatorCommand); - Operators = new BindableCollection(); - - ShowDataModelValues = settingsService.GetSetting("ProfileEditor.ShowDataModelValues"); - } - - public DataModelConditionPredicate DataModelConditionPredicate => (DataModelConditionPredicate) Model; - public PluginSetting ShowDataModelValues { get; } - - public bool CanSelectOperator => DataModelConditionPredicate.LeftPath is {IsValid: true}; - - public BaseConditionOperator SelectedOperator - { - get => _selectedOperator; - set => SetAndNotify(ref _selectedOperator, value); - } - - public DataModelDynamicViewModel RightSideSelectionViewModel - { - get => _rightSideSelectionViewModel; - set => SetAndNotify(ref _rightSideSelectionViewModel, value); - } - - public DataModelStaticViewModel RightSideInputViewModel - { - get => _rightSideInputViewModel; - set => SetAndNotify(ref _rightSideInputViewModel, value); - } - - public DelegateCommand SelectOperatorCommand { get; } - public BindableCollection Operators { get; } - - protected SolidColorBrush LeftSideColor { get; set; } - - public override void Evaluate() - { - IsConditionMet = DataModelConditionPredicate.Evaluate(); - } - - public override void Delete() - { - base.Delete(); - _profileEditorService.SaveSelectedProfileElement(); - } - - public virtual void Initialize() - { - LeftSideSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_modules); - LeftSideSelectionViewModel.PropertySelected += LeftSideOnPropertySelected; - if (LeftSideColor != null) - LeftSideSelectionViewModel.ButtonBrush = LeftSideColor; - - // Determine which types are currently supported - _supportedInputTypes = GetSupportedInputTypes(); - - Update(); - } - - public override void Update() - { - LeftSideSelectionViewModel.FilterTypes = _supportedInputTypes.ToArray(); - LeftSideSelectionViewModel.ChangeDataModelPath(DataModelConditionPredicate.LeftPath); - - Type leftSideType = GetLeftSideType(); - - // Get the supported operators - Operators.Clear(); - Operators.AddRange(_conditionOperatorService.GetConditionOperatorsForType(leftSideType ?? typeof(object), ConditionParameterSide.Left)); - if (DataModelConditionPredicate.Operator == null) - DataModelConditionPredicate.UpdateOperator(Operators.FirstOrDefault()); - // The core doesn't care about best matches so if there is a new preferred operator, use that instead - else if (!Operators.Contains(DataModelConditionPredicate.Operator)) - DataModelConditionPredicate.UpdateOperator(Operators.FirstOrDefault(o => o.Description == DataModelConditionPredicate.Operator.Description) ?? Operators.FirstOrDefault()); - - NotifyOfPropertyChange(nameof(CanSelectOperator)); - SelectedOperator = DataModelConditionPredicate.Operator; - - // Without a selected operator or one that supports a right side, leave the right side input empty - if (SelectedOperator?.RightSideType == null) - { - DisposeRightSideStaticViewModel(); - DisposeRightSideDynamicViewModel(); - return; - } - - // Ensure the right side has the proper VM - if (DataModelConditionPredicate.PredicateType == ProfileRightSideType.Dynamic) - { - DisposeRightSideStaticViewModel(); - if (RightSideSelectionViewModel == null) - CreateRightSideSelectionViewModel(); - - RightSideSelectionViewModel.ChangeDataModelPath(DataModelConditionPredicate.RightPath); - RightSideSelectionViewModel.FilterTypes = new[] {SelectedOperator.RightSideType}; - } - else - { - DisposeRightSideDynamicViewModel(); - if (RightSideInputViewModel == null) - CreateRightSideInputViewModel(); - - Type preferredType = DataModelConditionPredicate.GetPreferredRightSideType(); - if (preferredType != null && RightSideInputViewModel.TargetType != preferredType) - RightSideInputViewModel.UpdateTargetType(preferredType); - - RightSideInputViewModel.Value = DataModelConditionPredicate.RightStaticValue; - } - } - - public void ApplyLeftSide() - { - Type newType = LeftSideSelectionViewModel.DataModelPath.GetPropertyType(); - bool converted = ConvertIfRequired(newType); - if (converted) - return; - - DataModelConditionPredicate.UpdateLeftSide(LeftSideSelectionViewModel.DataModelPath); - _profileEditorService.SaveSelectedProfileElement(); - - SelectedOperator = DataModelConditionPredicate.Operator; - Update(); - } - - public void ApplyRightSideDynamic() - { - DataModelConditionPredicate.UpdateRightSideDynamic(RightSideSelectionViewModel.DataModelPath); - _profileEditorService.SaveSelectedProfileElement(); - - Update(); - } - - public void ApplyRightSideStatic(object value) - { - DataModelConditionPredicate.UpdateRightSideStatic(value); - _profileEditorService.SaveSelectedProfileElement(); - - Update(); - } - - public void ApplyOperator() - { - DataModelConditionPredicate.UpdateOperator(SelectedOperator); - _profileEditorService.SaveSelectedProfileElement(); - - Update(); - } - - protected abstract List GetSupportedInputTypes(); - protected abstract Type GetLeftSideType(); - - protected virtual List GetExtraRightSideDataModelViewModels() - { - return null; - } - - private void ExecuteSelectOperatorCommand(object context) - { - if (!(context is BaseConditionOperator dataModelConditionOperator)) - return; - - SelectedOperator = dataModelConditionOperator; - ApplyOperator(); - } - - public override void UpdateModules() - { - if (LeftSideSelectionViewModel != null) - { - LeftSideSelectionViewModel.PropertySelected -= LeftSideOnPropertySelected; - LeftSideSelectionViewModel.Dispose(); - LeftSideSelectionViewModel = null; - } - DisposeRightSideStaticViewModel(); - DisposeRightSideDynamicViewModel(); - - // If the modules changed the paths may no longer be valid if they targeted a module no longer available, in that case clear the path - if (DataModelConditionPredicate.LeftPath?.Target != null && !DataModelConditionPredicate.LeftPath.Target.IsExpansion && !_modules.Contains(DataModelConditionPredicate.LeftPath.Target.Module)) - DataModelConditionPredicate.UpdateLeftSide(null); - if (DataModelConditionPredicate.RightPath?.Target != null && !DataModelConditionPredicate.RightPath.Target.IsExpansion && !_modules.Contains(DataModelConditionPredicate.RightPath.Target.Module)) - DataModelConditionPredicate.UpdateRightSideDynamic(null); - - Initialize(); - } - - #region IDisposable - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - if (LeftSideSelectionViewModel != null) - { - LeftSideSelectionViewModel.PropertySelected -= LeftSideOnPropertySelected; - LeftSideSelectionViewModel.Dispose(); - LeftSideSelectionViewModel = null; - } - - DisposeRightSideStaticViewModel(); - DisposeRightSideDynamicViewModel(); - } - } - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - #endregion - - #region View model creation - - private void CreateRightSideSelectionViewModel() - { - RightSideSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_modules); - RightSideSelectionViewModel.ButtonBrush = (SolidColorBrush) Application.Current.FindResource("PrimaryHueMidBrush"); - RightSideSelectionViewModel.DisplaySwitchButton = true; - RightSideSelectionViewModel.PropertySelected += RightSideOnPropertySelected; - RightSideSelectionViewModel.SwitchToStaticRequested += RightSideSelectionViewModelOnSwitchToStaticRequested; - - List extra = GetExtraRightSideDataModelViewModels(); - if (extra != null) - RightSideSelectionViewModel.ExtraDataModelViewModels.AddRange(extra); - } - - private void CreateRightSideInputViewModel() - { - Type preferredType = DataModelConditionPredicate.GetPreferredRightSideType(); - RightSideInputViewModel = _dataModelUIService.GetStaticInputViewModel(preferredType, LeftSideSelectionViewModel.DataModelPath?.GetPropertyDescription()); - RightSideInputViewModel.ButtonBrush = (SolidColorBrush) Application.Current.FindResource("PrimaryHueMidBrush"); - RightSideInputViewModel.DisplaySwitchButton = true; - RightSideInputViewModel.ValueUpdated += RightSideOnValueEntered; - RightSideInputViewModel.SwitchToDynamicRequested += RightSideInputViewModelOnSwitchToDynamicRequested; - } - - private void DisposeRightSideStaticViewModel() - { - if (RightSideInputViewModel == null) - return; - RightSideInputViewModel.ValueUpdated -= RightSideOnValueEntered; - RightSideInputViewModel.SwitchToDynamicRequested -= RightSideInputViewModelOnSwitchToDynamicRequested; - RightSideInputViewModel.Dispose(); - RightSideInputViewModel = null; - } - - private void DisposeRightSideDynamicViewModel() - { - if (RightSideSelectionViewModel == null) - return; - RightSideSelectionViewModel.PropertySelected -= RightSideOnPropertySelected; - RightSideSelectionViewModel.SwitchToStaticRequested -= RightSideSelectionViewModelOnSwitchToStaticRequested; - RightSideSelectionViewModel.Dispose(); - RightSideSelectionViewModel = null; - } - - #endregion - - #region Event handlers - - private void LeftSideOnPropertySelected(object sender, DataModelInputDynamicEventArgs e) - { - ApplyLeftSide(); - } - - private void RightSideOnPropertySelected(object sender, DataModelInputDynamicEventArgs e) - { - ApplyRightSideDynamic(); - } - - private void RightSideOnValueEntered(object sender, DataModelInputStaticEventArgs e) - { - ApplyRightSideStatic(e.Value); - } - - private void RightSideSelectionViewModelOnSwitchToStaticRequested(object sender, EventArgs e) - { - DataModelConditionPredicate.PredicateType = ProfileRightSideType.Static; - - // Ensure the right static value is never null when the preferred type is a value type - Type preferredType = DataModelConditionPredicate.GetPreferredRightSideType(); - if (DataModelConditionPredicate.RightStaticValue == null && preferredType != null && preferredType.IsValueType) - DataModelConditionPredicate.UpdateRightSideStatic(preferredType.GetDefault()); - - Update(); - } - - private void RightSideInputViewModelOnSwitchToDynamicRequested(object sender, EventArgs e) - { - DataModelConditionPredicate.PredicateType = ProfileRightSideType.Dynamic; - Update(); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Abstract/DataModelConditionViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Abstract/DataModelConditionViewModel.cs deleted file mode 100644 index 9f7db7a2d..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Abstract/DataModelConditionViewModel.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using Artemis.Core; -using Artemis.UI.Shared.Input; -using Stylet; - -namespace Artemis.UI.Screens.ProfileEditor.Conditions.Abstract -{ - public abstract class DataModelConditionViewModel : Conductor.Collection.AllActive - { - private DataModelDynamicViewModel _leftSideSelectionViewModel; - private bool _isConditionMet; - - protected DataModelConditionViewModel(DataModelConditionPart model) - { - Model = model; - } - - public DataModelConditionPart Model { get; } - - public DataModelDynamicViewModel LeftSideSelectionViewModel - { - get => _leftSideSelectionViewModel; - set => SetAndNotify(ref _leftSideSelectionViewModel, value); - } - - public bool IsConditionMet - { - get => _isConditionMet; - set => SetAndNotify(ref _isConditionMet, value); - } - - - public abstract void Update(); - - public abstract void Evaluate(); - - public virtual void Delete() - { - Model.Parent.RemoveChild(Model); - ((DataModelConditionViewModel) Parent).Update(); - } - - protected bool ConvertIfRequired(Type newType) - { - if (newType == null) - return false; - - if (!(Parent is DataModelConditionGroupViewModel groupViewModel)) - return false; - - // List - if (newType.IsGenericEnumerable()) - { - if (this is DataModelConditionListViewModel) - return false; - groupViewModel.ConvertToConditionList(this); - return true; - } - - // Predicate - if (this is DataModelConditionPredicateViewModel) - return false; - groupViewModel.ConvertToPredicate(this); - return true; - } - - public abstract void UpdateModules(); - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionEventView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionEventView.xaml deleted file mode 100644 index 0c93f64b5..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionEventView.xaml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionEventViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionEventViewModel.cs deleted file mode 100644 index d50edbbc9..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionEventViewModel.cs +++ /dev/null @@ -1,147 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Windows.Media; -using Artemis.Core; -using Artemis.Core.Modules; -using Artemis.UI.Ninject.Factories; -using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract; -using Artemis.UI.Shared; -using Artemis.UI.Shared.Services; - -namespace Artemis.UI.Screens.ProfileEditor.Conditions -{ - public sealed class DataModelConditionEventViewModel : DataModelConditionViewModel, IDisposable - { - private readonly IDataModelConditionsVmFactory _dataModelConditionsVmFactory; - private readonly IDataModelUIService _dataModelUIService; - private readonly List _modules; - private readonly IProfileEditorService _profileEditorService; - private DateTime _lastTrigger; - private string _triggerPastParticiple; - - public DataModelConditionEventViewModel(DataModelConditionEvent dataModelConditionEvent, - List modules, - IProfileEditorService profileEditorService, - IDataModelUIService dataModelUIService, - IDataModelConditionsVmFactory dataModelConditionsVmFactory) : base(dataModelConditionEvent) - { - _modules = modules; - _profileEditorService = profileEditorService; - _dataModelUIService = dataModelUIService; - _dataModelConditionsVmFactory = dataModelConditionsVmFactory; - - _lastTrigger = DataModelConditionEvent.LastTrigger; - } - - public DataModelConditionEvent DataModelConditionEvent => (DataModelConditionEvent) Model; - - public DateTime LastTrigger - { - get => _lastTrigger; - set => SetAndNotify(ref _lastTrigger, value); - } - - public string TriggerPastParticiple - { - get => _triggerPastParticiple; - set => SetAndNotify(ref _triggerPastParticiple, value); - } - - public void Initialize() - { - LeftSideSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_modules); - LeftSideSelectionViewModel.PropertySelected += LeftSideSelectionViewModelOnPropertySelected; - LeftSideSelectionViewModel.LoadEventChildren = false; - - IReadOnlyCollection editors = _dataModelUIService.RegisteredDataModelEditors; - List supportedInputTypes = editors.Select(e => e.SupportedType).ToList(); - supportedInputTypes.AddRange(editors.Where(e => e.CompatibleConversionTypes != null).SelectMany(e => e.CompatibleConversionTypes)); - supportedInputTypes.Add(typeof(DataModelEvent)); - supportedInputTypes.Add(typeof(DataModelEvent<>)); - - LeftSideSelectionViewModel.FilterTypes = supportedInputTypes.ToArray(); - LeftSideSelectionViewModel.ButtonBrush = new SolidColorBrush(Color.FromRgb(185, 164, 10)); - LeftSideSelectionViewModel.Placeholder = "Select an event"; - - Update(); - } - - public override void Update() - { - LeftSideSelectionViewModel.ChangeDataModelPath(DataModelConditionEvent.EventPath); - - // Remove VMs of effects no longer applied on the layer - Items.RemoveRange(Items.Where(c => !DataModelConditionEvent.Children.Contains(c.Model)).ToList()); - - if (DataModelConditionEvent.EventPath == null || !DataModelConditionEvent.EventPath.IsValid) - return; - - TriggerPastParticiple = DataModelConditionEvent.GetDataModelEvent()?.TriggerPastParticiple; - List viewModels = new(); - foreach (DataModelConditionPart childModel in Model.Children) - { - if (Items.Any(c => c.Model == childModel)) - continue; - if (!(childModel is DataModelConditionGroup dataModelConditionGroup)) - continue; - - DataModelConditionGroupViewModel viewModel = _dataModelConditionsVmFactory.DataModelConditionGroupViewModel(dataModelConditionGroup, ConditionGroupType.Event, _modules); - viewModel.IsRootGroup = true; - viewModels.Add(viewModel); - } - - if (viewModels.Any()) - Items.AddRange(viewModels); - - foreach (DataModelConditionViewModel childViewModel in Items) - childViewModel.Update(); - } - - public override void Evaluate() - { - LastTrigger = DataModelConditionEvent.LastTrigger; - IsConditionMet = DataModelConditionEvent.Evaluate(); - } - - public void ApplyEvent() - { - DataModelConditionEvent.UpdateEvent(LeftSideSelectionViewModel.DataModelPath); - _profileEditorService.SaveSelectedProfileElement(); - - Update(); - } - - public override void UpdateModules() - { - LeftSideSelectionViewModel.Dispose(); - LeftSideSelectionViewModel.PropertySelected -= LeftSideSelectionViewModelOnPropertySelected; - Initialize(); - } - - protected override void OnInitialActivate() - { - Initialize(); - base.OnInitialActivate(); - } - - #region Event handlers - - private void LeftSideSelectionViewModelOnPropertySelected(object sender, DataModelInputDynamicEventArgs e) - { - ApplyEvent(); - } - - #endregion - - #region IDisposable - - public void Dispose() - { - LeftSideSelectionViewModel.Dispose(); - LeftSideSelectionViewModel.PropertySelected -= LeftSideSelectionViewModelOnPropertySelected; - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupView.xaml deleted file mode 100644 index 7afa0ef9a..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupView.xaml +++ /dev/null @@ -1,165 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupViewModel.cs deleted file mode 100644 index 6b0e8c1ab..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupViewModel.cs +++ /dev/null @@ -1,259 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Artemis.Core; -using Artemis.Core.Modules; -using Artemis.Core.Services; -using Artemis.UI.Extensions; -using Artemis.UI.Ninject.Factories; -using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract; -using Artemis.UI.Shared.Services; -using Humanizer; -using Stylet; - -namespace Artemis.UI.Screens.ProfileEditor.Conditions -{ - public class DataModelConditionGroupViewModel : DataModelConditionViewModel - { - private readonly IDataModelConditionsVmFactory _dataModelConditionsVmFactory; - private readonly List _modules; - private readonly ICoreService _coreService; - private readonly IProfileEditorService _profileEditorService; - private bool _isEventGroup; - private bool _isRootGroup; - - public DataModelConditionGroupViewModel(DataModelConditionGroup dataModelConditionGroup, - ConditionGroupType groupType, - List modules, - ICoreService coreService, - IProfileEditorService profileEditorService, - IDataModelConditionsVmFactory dataModelConditionsVmFactory) - : base(dataModelConditionGroup) - { - GroupType = groupType; - _modules = modules; - _coreService = coreService; - _profileEditorService = profileEditorService; - _dataModelConditionsVmFactory = dataModelConditionsVmFactory; - - Items.CollectionChanged += (_, _) => NotifyOfPropertyChange(nameof(DisplayBooleanOperator)); - } - - public ConditionGroupType GroupType { get; } - public DataModelConditionGroup DataModelConditionGroup => (DataModelConditionGroup) Model; - - public bool IsRootGroup - { - get => _isRootGroup; - set - { - if (!SetAndNotify(ref _isRootGroup, value)) return; - NotifyOfPropertyChange(nameof(CanAddEventCondition)); - } - } - - public bool CanAddEventCondition => IsRootGroup && GroupType == ConditionGroupType.General; - - public bool IsEventGroup - { - get => _isEventGroup; - set - { - SetAndNotify(ref _isEventGroup, value); - NotifyOfPropertyChange(nameof(DisplayEvaluationResult)); - } - } - - public bool DisplayBooleanOperator => Items.Count > 1; - public bool DisplayEvaluationResult => GroupType == ConditionGroupType.General && !IsEventGroup; - public string SelectedBooleanOperator => DataModelConditionGroup.BooleanOperator.Humanize(); - - public void SelectBooleanOperator(string type) - { - BooleanOperator enumValue = Enum.Parse(type); - DataModelConditionGroup.BooleanOperator = enumValue; - NotifyOfPropertyChange(nameof(SelectedBooleanOperator)); - - _profileEditorService.SaveSelectedProfileElement(); - } - - public void AddCondition() - { - switch (GroupType) - { - case ConditionGroupType.General: - DataModelConditionGroup.AddChild(new DataModelConditionGeneralPredicate(DataModelConditionGroup, ProfileRightSideType.Dynamic)); - break; - case ConditionGroupType.List: - DataModelConditionGroup.AddChild(new DataModelConditionListPredicate(DataModelConditionGroup, ProfileRightSideType.Dynamic)); - break; - case ConditionGroupType.Event: - DataModelConditionGroup.AddChild(new DataModelConditionEventPredicate(DataModelConditionGroup, ProfileRightSideType.Dynamic)); - break; - default: - throw new ArgumentOutOfRangeException(); - } - - Update(); - _profileEditorService.SaveSelectedProfileElement(); - } - - public void AddEventCondition() - { - if (!CanAddEventCondition) - return; - - // Find a good spot for the event, behind the last existing event - int index = 0; - DataModelConditionPart existing = DataModelConditionGroup.Children.LastOrDefault(c => c is DataModelConditionEvent); - if (existing != null) - index = DataModelConditionGroup.Children.IndexOf(existing) + 1; - - DataModelConditionGroup.AddChild(new DataModelConditionEvent(DataModelConditionGroup), index); - - Update(); - _profileEditorService.SaveSelectedProfileElement(); - } - - public void AddGroup() - { - DataModelConditionGroup.AddChild(new DataModelConditionGroup(DataModelConditionGroup)); - - Update(); - _profileEditorService.SaveSelectedProfileElement(); - } - - public override void Update() - { - NotifyOfPropertyChange(nameof(SelectedBooleanOperator)); - // Remove VMs of effects no longer applied on the layer - List toRemove = Items.Where(c => !DataModelConditionGroup.Children.Contains(c.Model)).ToList(); - if (toRemove.Any()) - Items.RemoveRange(toRemove); - - foreach (DataModelConditionPart childModel in Model.Children) - { - if (Items.Any(c => c.Model == childModel)) - continue; - - switch (childModel) - { - case DataModelConditionGroup dataModelConditionGroup: - Items.Add(_dataModelConditionsVmFactory.DataModelConditionGroupViewModel(dataModelConditionGroup, GroupType, _modules)); - break; - case DataModelConditionList dataModelConditionList: - Items.Add(_dataModelConditionsVmFactory.DataModelConditionListViewModel(dataModelConditionList, _modules)); - break; - case DataModelConditionEvent dataModelConditionEvent: - Items.Add(_dataModelConditionsVmFactory.DataModelConditionEventViewModel(dataModelConditionEvent, _modules)); - break; - case DataModelConditionGeneralPredicate dataModelConditionGeneralPredicate: - Items.Add(_dataModelConditionsVmFactory.DataModelConditionGeneralPredicateViewModel(dataModelConditionGeneralPredicate, _modules)); - break; - case DataModelConditionListPredicate dataModelConditionListPredicate: - Items.Add(_dataModelConditionsVmFactory.DataModelConditionListPredicateViewModel(dataModelConditionListPredicate, _modules)); - break; - case DataModelConditionEventPredicate dataModelConditionEventPredicate: - Items.Add(_dataModelConditionsVmFactory.DataModelConditionEventPredicateViewModel(dataModelConditionEventPredicate, _modules)); - break; - } - } - - // Ensure the items are in the same order as on the model - ((BindableCollection) Items).Sort(i => Model.Children.IndexOf(i.Model)); - foreach (DataModelConditionViewModel childViewModel in Items) - childViewModel.Update(); - - IsEventGroup = Items.Any(i => i is DataModelConditionEventViewModel); - if (IsEventGroup) - { - if (DataModelConditionGroup.BooleanOperator != BooleanOperator.And) - SelectBooleanOperator("And"); - } - - OnUpdated(); - } - - public override void Evaluate() - { - IsConditionMet = DataModelConditionGroup.Evaluate(); - foreach (DataModelConditionViewModel dataModelConditionViewModel in Items) - dataModelConditionViewModel.Evaluate(); - } - - public override void UpdateModules() - { - foreach (DataModelConditionViewModel dataModelConditionViewModel in Items) - dataModelConditionViewModel.UpdateModules(); - } - - public void ConvertToConditionList(DataModelConditionViewModel predicateViewModel) - { - // Store the old index and remove the old predicate - int index = DataModelConditionGroup.Children.IndexOf(predicateViewModel.Model); - DataModelConditionGroup.RemoveChild(predicateViewModel.Model); - - // Insert a list in the same position - DataModelConditionList list = new(DataModelConditionGroup); - list.UpdateList(predicateViewModel.LeftSideSelectionViewModel.DataModelPath); - DataModelConditionGroup.AddChild(list, index); - - // Update to switch the VMs - Update(); - } - - public void ConvertToPredicate(DataModelConditionViewModel listViewModel) - { - // Store the old index and remove the old predicate - int index = DataModelConditionGroup.Children.IndexOf(listViewModel.Model); - DataModelConditionGroup.RemoveChild(listViewModel.Model); - - // Insert a list in the same position - DataModelConditionGeneralPredicate predicate = new(DataModelConditionGroup, ProfileRightSideType.Dynamic); - predicate.UpdateLeftSide(listViewModel.LeftSideSelectionViewModel.DataModelPath); - DataModelConditionGroup.AddChild(predicate, index); - - // Update to switch the VMs - Update(); - } - - private void CoreServiceOnFrameRendered(object sender, FrameRenderedEventArgs e) - { - if (IsRootGroup) - Evaluate(); - } - - public event EventHandler Updated; - - #region Overrides of Screen - - /// - protected override void OnInitialActivate() - { - base.OnInitialActivate(); - Update(); - _coreService.FrameRendered += CoreServiceOnFrameRendered; - } - - /// - protected override void OnClose() - { - _coreService.FrameRendered -= CoreServiceOnFrameRendered; - base.OnClose(); - } - - #endregion - - protected virtual void OnUpdated() - { - Updated?.Invoke(this, EventArgs.Empty); - } - } - - public enum ConditionGroupType - { - General, - List, - Event - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListView.xaml deleted file mode 100644 index ac405354f..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListView.xaml +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListView.xaml.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListView.xaml.cs deleted file mode 100644 index 23a1fe9e6..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListView.xaml.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Windows; -using System.Windows.Controls; - -namespace Artemis.UI.Screens.ProfileEditor.Conditions -{ - /// - /// Interaction logic for DataModelConditionListView.xaml - /// - public partial class DataModelConditionListView : UserControl - { - public DataModelConditionListView() - { - InitializeComponent(); - } - - private void PropertyButton_OnClick(object sender, RoutedEventArgs e) - { - // DataContext is not set when using left button, I don't know why but there it is - if (sender is Button button && button.ContextMenu != null) - { - button.ContextMenu.DataContext = button.DataContext; - button.ContextMenu.IsOpen = true; - } - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListViewModel.cs deleted file mode 100644 index c4e244b66..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListViewModel.cs +++ /dev/null @@ -1,165 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Windows.Media; -using Artemis.Core; -using Artemis.Core.Modules; -using Artemis.UI.Ninject.Factories; -using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract; -using Artemis.UI.Shared; -using Artemis.UI.Shared.Services; -using Humanizer; - -namespace Artemis.UI.Screens.ProfileEditor.Conditions -{ - public sealed class DataModelConditionListViewModel : DataModelConditionViewModel, IDisposable - { - private readonly IDataModelConditionsVmFactory _dataModelConditionsVmFactory; - private readonly IDataModelUIService _dataModelUIService; - private readonly List _modules; - private readonly IProfileEditorService _profileEditorService; - - public DataModelConditionListViewModel(DataModelConditionList dataModelConditionList, - List modules, - IProfileEditorService profileEditorService, - IDataModelUIService dataModelUIService, - IDataModelConditionsVmFactory dataModelConditionsVmFactory) : base(dataModelConditionList) - { - _modules = modules; - _profileEditorService = profileEditorService; - _dataModelUIService = dataModelUIService; - _dataModelConditionsVmFactory = dataModelConditionsVmFactory; - } - - public DataModelConditionList DataModelConditionList => (DataModelConditionList) Model; - - public string SelectedListOperator => DataModelConditionList.ListOperator.Humanize(); - - public void SelectListOperator(string type) - { - ListOperator enumValue = Enum.Parse(type); - DataModelConditionList.ListOperator = enumValue; - NotifyOfPropertyChange(nameof(SelectedListOperator)); - - _profileEditorService.SaveSelectedProfileElement(); - } - - public void AddCondition() - { - DataModelConditionList.AddChild(new DataModelConditionGeneralPredicate(DataModelConditionList, ProfileRightSideType.Dynamic)); - - Update(); - _profileEditorService.SaveSelectedProfileElement(); - } - - public void AddGroup() - { - DataModelConditionList.AddChild(new DataModelConditionGroup(DataModelConditionList)); - - Update(); - _profileEditorService.SaveSelectedProfileElement(); - } - - public override void Evaluate() - { - IsConditionMet = DataModelConditionList.Evaluate(); - foreach (DataModelConditionViewModel dataModelConditionViewModel in Items) - dataModelConditionViewModel.Evaluate(); - } - - public override void Delete() - { - base.Delete(); - _profileEditorService.SaveSelectedProfileElement(); - } - - /// - public override void UpdateModules() - { - foreach (DataModelConditionViewModel dataModelConditionViewModel in Items) - dataModelConditionViewModel.UpdateModules(); - } - - public void Initialize() - { - LeftSideSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_modules); - LeftSideSelectionViewModel.PropertySelected += LeftSideSelectionViewModelOnPropertySelected; - - IReadOnlyCollection editors = _dataModelUIService.RegisteredDataModelEditors; - List supportedInputTypes = editors.Select(e => e.SupportedType).ToList(); - supportedInputTypes.AddRange(editors.Where(e => e.CompatibleConversionTypes != null).SelectMany(e => e.CompatibleConversionTypes)); - supportedInputTypes.Add(typeof(IEnumerable<>)); - - LeftSideSelectionViewModel.FilterTypes = supportedInputTypes.ToArray(); - LeftSideSelectionViewModel.ButtonBrush = new SolidColorBrush(Color.FromRgb(71, 108, 188)); - LeftSideSelectionViewModel.Placeholder = "Select a list"; - - Update(); - } - - public void ApplyList() - { - Type newType = LeftSideSelectionViewModel.DataModelPath.GetPropertyType(); - bool converted = ConvertIfRequired(newType); - if (converted) - return; - - DataModelConditionList.UpdateList(LeftSideSelectionViewModel.DataModelPath); - _profileEditorService.SaveSelectedProfileElement(); - - Update(); - } - - public override void Update() - { - LeftSideSelectionViewModel.ChangeDataModelPath(DataModelConditionList.ListPath); - NotifyOfPropertyChange(nameof(SelectedListOperator)); - - // Remove VMs of effects no longer applied on the layer - Items.RemoveRange(Items.Where(c => !DataModelConditionList.Children.Contains(c.Model)).ToList()); - - if (DataModelConditionList.ListPath == null || !DataModelConditionList.ListPath.IsValid) - return; - - List viewModels = new(); - foreach (DataModelConditionPart childModel in Model.Children) - { - if (Items.Any(c => c.Model == childModel)) - continue; - if (!(childModel is DataModelConditionGroup dataModelConditionGroup)) - continue; - - DataModelConditionGroupViewModel viewModel = _dataModelConditionsVmFactory.DataModelConditionGroupViewModel(dataModelConditionGroup, ConditionGroupType.List, _modules); - viewModel.IsRootGroup = true; - viewModels.Add(viewModel); - } - - if (viewModels.Any()) - Items.AddRange(viewModels); - - foreach (DataModelConditionViewModel childViewModel in Items) - childViewModel.Update(); - } - - protected override void OnInitialActivate() - { - Initialize(); - base.OnInitialActivate(); - } - - private void LeftSideSelectionViewModelOnPropertySelected(object sender, DataModelInputDynamicEventArgs e) - { - ApplyList(); - } - - #region IDisposable - - public void Dispose() - { - LeftSideSelectionViewModel.Dispose(); - LeftSideSelectionViewModel.PropertySelected -= LeftSideSelectionViewModelOnPropertySelected; - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionEventPredicateView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionEventPredicateView.xaml deleted file mode 100644 index a90fc0b13..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionEventPredicateView.xaml +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionEventPredicateView.xaml.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionEventPredicateView.xaml.cs deleted file mode 100644 index 5ce43cf28..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionEventPredicateView.xaml.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Windows; -using System.Windows.Controls; - -namespace Artemis.UI.Screens.ProfileEditor.Conditions -{ - /// - /// Interaction logic for DataModelConditionEventPredicateView.xaml - /// - public partial class DataModelConditionEventPredicateView : UserControl - { - public DataModelConditionEventPredicateView() - { - InitializeComponent(); - } - - private void PropertyButton_OnClick(object sender, RoutedEventArgs e) - { - // DataContext is not set when using left button, I don't know why but there it is - if (sender is Button button && button.ContextMenu != null) - { - button.ContextMenu.DataContext = button.DataContext; - button.ContextMenu.IsOpen = true; - } - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionEventPredicateViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionEventPredicateViewModel.cs deleted file mode 100644 index 78bd7b625..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionEventPredicateViewModel.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Windows.Media; -using Artemis.Core; -using Artemis.Core.Modules; -using Artemis.Core.Services; -using Artemis.UI.Extensions; -using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract; -using Artemis.UI.Shared; -using Artemis.UI.Shared.Services; -using Stylet; - -namespace Artemis.UI.Screens.ProfileEditor.Conditions -{ - public class DataModelConditionEventPredicateViewModel : DataModelConditionPredicateViewModel - { - private readonly IDataModelUIService _dataModelUIService; - - public DataModelConditionEventPredicateViewModel(DataModelConditionEventPredicate dataModelConditionEventPredicate, - List modules, - IProfileEditorService profileEditorService, - IDataModelUIService dataModelUIService, - IConditionOperatorService conditionOperatorService, - ISettingsService settingsService) - : base(dataModelConditionEventPredicate, modules, profileEditorService, dataModelUIService, conditionOperatorService, settingsService) - { - _dataModelUIService = dataModelUIService; - - LeftSideColor = new SolidColorBrush(Color.FromRgb(185, 164, 10)); - } - - public DataModelConditionEventPredicate DataModelConditionEventPredicate => (DataModelConditionEventPredicate) Model; - - public override void Initialize() - { - base.Initialize(); - - DataModelPropertiesViewModel eventDataModel = GetEventDataModel(); - LeftSideSelectionViewModel.ChangeDataModel(eventDataModel); - } - - protected override void OnInitialActivate() - { - base.OnInitialActivate(); - Initialize(); - } - - protected override List GetSupportedInputTypes() - { - IReadOnlyCollection editors = _dataModelUIService.RegisteredDataModelEditors; - List supportedInputTypes = editors.Select(e => e.SupportedType).ToList(); - supportedInputTypes.AddRange(editors.Where(e => e.CompatibleConversionTypes != null).SelectMany(e => e.CompatibleConversionTypes)); - - return supportedInputTypes; - } - - protected override Type GetLeftSideType() - { - return LeftSideSelectionViewModel.DataModelPath?.GetPropertyType(); - } - - protected override List GetExtraRightSideDataModelViewModels() - { - // Only the VM housing the event arguments is expected here which is a DataModelPropertiesViewModel - return GetEventDataModel().Children.Cast().ToList(); - } - - private DataModelPropertiesViewModel GetEventDataModel() - { - EventPredicateWrapperDataModel wrapper = EventPredicateWrapperDataModel.Create( - DataModelConditionEventPredicate.DataModelConditionEvent.EventArgumentType - ); - - return wrapper.CreateViewModel(_dataModelUIService, new DataModelUpdateConfiguration(false)); - } - - public override void Evaluate() - { - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateView.xaml deleted file mode 100644 index eab1e69d3..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateView.xaml +++ /dev/null @@ -1,115 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateView.xaml.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateView.xaml.cs deleted file mode 100644 index 22411462b..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateView.xaml.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Windows; -using System.Windows.Controls; - -namespace Artemis.UI.Screens.ProfileEditor.Conditions -{ - /// - /// Interaction logic for DataModelConditionGeneralPredicateView.xaml - /// - public partial class DataModelConditionGeneralPredicateView : UserControl - { - public DataModelConditionGeneralPredicateView() - { - InitializeComponent(); - } - - private void PropertyButton_OnClick(object sender, RoutedEventArgs e) - { - // DataContext is not set when using left button, I don't know why but there it is - if (sender is Button button && button.ContextMenu != null) - { - button.ContextMenu.DataContext = button.DataContext; - button.ContextMenu.IsOpen = true; - } - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateViewModel.cs deleted file mode 100644 index f02494b7f..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateViewModel.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Artemis.Core; -using Artemis.Core.Modules; -using Artemis.Core.Services; -using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract; -using Artemis.UI.Shared; -using Artemis.UI.Shared.Services; - -namespace Artemis.UI.Screens.ProfileEditor.Conditions -{ - public class DataModelConditionGeneralPredicateViewModel : DataModelConditionPredicateViewModel - { - private readonly IDataModelUIService _dataModelUIService; - - public DataModelConditionGeneralPredicateViewModel(DataModelConditionGeneralPredicate dataModelConditionGeneralPredicate, - List modules, - IProfileEditorService profileEditorService, - IDataModelUIService dataModelUIService, - IConditionOperatorService conditionOperatorService, - ISettingsService settingsService) - : base(dataModelConditionGeneralPredicate, modules, profileEditorService, dataModelUIService, conditionOperatorService, settingsService) - { - _dataModelUIService = dataModelUIService; - } - - protected override void OnInitialActivate() - { - Initialize(); - } - - protected override List GetSupportedInputTypes() - { - IReadOnlyCollection editors = _dataModelUIService.RegisteredDataModelEditors; - List supportedInputTypes = editors.Select(e => e.SupportedType).ToList(); - supportedInputTypes.AddRange(editors.Where(e => e.CompatibleConversionTypes != null).SelectMany(e => e.CompatibleConversionTypes)); - supportedInputTypes.Add(typeof(IEnumerable<>)); - - return supportedInputTypes; - } - - protected override Type GetLeftSideType() - { - return LeftSideSelectionViewModel.DataModelPath?.GetPropertyType(); - } - - public override void Evaluate() - { - IsConditionMet = DataModelConditionPredicate.Evaluate(); - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionListPredicateView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionListPredicateView.xaml deleted file mode 100644 index 5a273a099..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionListPredicateView.xaml +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionListPredicateView.xaml.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionListPredicateView.xaml.cs deleted file mode 100644 index 44c02f06b..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionListPredicateView.xaml.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Windows; -using System.Windows.Controls; - -namespace Artemis.UI.Screens.ProfileEditor.Conditions -{ - /// - /// Interaction logic for DataModelConditionListPredicateView.xaml - /// - public partial class DataModelConditionListPredicateView : UserControl - { - public DataModelConditionListPredicateView() - { - InitializeComponent(); - } - - private void PropertyButton_OnClick(object sender, RoutedEventArgs e) - { - // DataContext is not set when using left button, I don't know why but there it is - if (sender is Button button && button.ContextMenu != null) - { - button.ContextMenu.DataContext = button.DataContext; - button.ContextMenu.IsOpen = true; - } - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionListPredicateViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionListPredicateViewModel.cs deleted file mode 100644 index 411e8f735..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionListPredicateViewModel.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Windows.Media; -using Artemis.Core; -using Artemis.Core.Modules; -using Artemis.Core.Services; -using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract; -using Artemis.UI.Shared; -using Artemis.UI.Shared.Services; - -namespace Artemis.UI.Screens.ProfileEditor.Conditions -{ - public class DataModelConditionListPredicateViewModel : DataModelConditionPredicateViewModel - { - private readonly IDataModelUIService _dataModelUIService; - - public DataModelConditionListPredicateViewModel(DataModelConditionListPredicate dataModelConditionListPredicate, - List modules, - IProfileEditorService profileEditorService, - IDataModelUIService dataModelUIService, - IConditionOperatorService conditionOperatorService, - ISettingsService settingsService) - : base(dataModelConditionListPredicate, modules, profileEditorService, dataModelUIService, conditionOperatorService, settingsService) - { - _dataModelUIService = dataModelUIService; - LeftSideColor = new SolidColorBrush(Color.FromRgb(71, 108, 188)); - } - - public DataModelConditionListPredicate DataModelConditionListPredicate => (DataModelConditionListPredicate) Model; - - public override void Initialize() - { - base.Initialize(); - - DataModelPropertiesViewModel listDataModel = GetListDataModel(); - LeftSideSelectionViewModel.ChangeDataModel(listDataModel); - - // If this is a primitive list the user doesn't have much to choose, so preselect the list item for them - if (DataModelConditionListPredicate.DataModelConditionList.IsPrimitiveList && DataModelConditionListPredicate.LeftPath == null) - { - DataModelConditionListPredicate.UpdateLeftSide(listDataModel.Children.FirstOrDefault()?.DataModelPath); - Update(); - } - } - - public override void Evaluate() - { - - } - - public override void UpdateModules() - { - foreach (DataModelConditionViewModel dataModelConditionViewModel in Items) - dataModelConditionViewModel.UpdateModules(); - } - - protected override void OnInitialActivate() - { - base.OnInitialActivate(); - Initialize(); - } - - protected override List GetSupportedInputTypes() - { - IReadOnlyCollection editors = _dataModelUIService.RegisteredDataModelEditors; - List supportedInputTypes = editors.Select(e => e.SupportedType).ToList(); - supportedInputTypes.AddRange(editors.Where(e => e.CompatibleConversionTypes != null).SelectMany(e => e.CompatibleConversionTypes)); - - return supportedInputTypes; - } - - protected override Type GetLeftSideType() - { - return DataModelConditionListPredicate.DataModelConditionList.IsPrimitiveList - ? DataModelConditionListPredicate.DataModelConditionList.ListType - : LeftSideSelectionViewModel.DataModelPath?.GetPropertyType(); - } - - protected override List GetExtraRightSideDataModelViewModels() - { - if (GetListDataModel()?.Children?.FirstOrDefault() is DataModelPropertiesViewModel listValue) - return new List {listValue}; - return null; - } - - private DataModelPropertiesViewModel GetListDataModel() - { - ListPredicateWrapperDataModel wrapper = ListPredicateWrapperDataModel.Create( - DataModelConditionListPredicate.DataModelConditionList.ListType!, - DataModelConditionListPredicate.DataModelConditionList.ListPath?.GetPropertyDescription()?.ListItemName - ); - - return wrapper.CreateViewModel(_dataModelUIService, new DataModelUpdateConfiguration(true)); - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs index 4fe3a8c9b..a587da7cc 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs @@ -2,14 +2,13 @@ using System.Windows.Input; using Artemis.Core; using Artemis.UI.Ninject.Factories; -using Artemis.UI.Screens.ProfileEditor.Conditions; using Artemis.UI.Shared; using Artemis.UI.Shared.Services; using Stylet; namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions { - public class DisplayConditionsViewModel : Conductor, IProfileEditorPanelViewModel + public class DisplayConditionsViewModel : Screen, IProfileEditorPanelViewModel { private readonly INodeVmFactory _nodeVmFactory; private readonly IProfileEditorService _profileEditorService; @@ -103,12 +102,6 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions NotifyOfPropertyChange(nameof(AlwaysFinishTimeline)); NotifyOfPropertyChange(nameof(ConditionBehaviourEnabled)); - if (renderProfileElement == null) - { - ActiveItem = null; - return; - } - RenderProfileElement.Timeline.PropertyChanged += TimelineOnPropertyChanged; } diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeView.xaml deleted file mode 100644 index 7fded19b2..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeView.xaml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeViewModel.cs deleted file mode 100644 index 0b6d1eeef..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeViewModel.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Linq; -using Artemis.Core; -using Artemis.UI.Extensions; -using Artemis.UI.Ninject.Factories; -using Artemis.UI.Shared.Services; -using Stylet; - -namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.ConditionalDataBinding -{ - public sealed class ConditionalDataBindingModeViewModel : Conductor>.Collection.AllActive, - IDataBindingModeViewModel - { - private readonly IDataBindingsVmFactory _dataBindingsVmFactory; - private readonly IProfileEditorService _profileEditorService; - private bool _updating; - - public ConditionalDataBindingModeViewModel(ConditionalDataBinding conditionalDataBinding, - IProfileEditorService profileEditorService, - IDataBindingsVmFactory dataBindingsVmFactory) - { - _profileEditorService = profileEditorService; - _dataBindingsVmFactory = dataBindingsVmFactory; - - ConditionalDataBinding = conditionalDataBinding; - } - - public ConditionalDataBinding ConditionalDataBinding { get; } - - public void AddCondition() - { - DataBindingCondition condition = ConditionalDataBinding.AddCondition(); - - // Find the VM of the new condition - DataBindingConditionViewModel viewModel = Items.First(c => c.DataBindingCondition == condition); - viewModel.ActiveItem.AddCondition(); - - _profileEditorService.SaveSelectedProfileElement(); - } - - public void RemoveCondition(DataBindingCondition dataBindingCondition) - { - ConditionalDataBinding.RemoveCondition(dataBindingCondition); - } - - protected override void OnInitialActivate() - { - Initialize(); - base.OnInitialActivate(); - } - - private void UpdateItems() - { - _updating = true; - - // Remove old VMs - List> toRemove = Items.Where(c => !ConditionalDataBinding.Conditions.Contains(c.DataBindingCondition)).ToList(); - foreach (DataBindingConditionViewModel dataBindingConditionViewModel in toRemove) - { - Items.Remove(dataBindingConditionViewModel); - dataBindingConditionViewModel.Dispose(); - } - - // Add missing VMs - foreach (DataBindingCondition condition in ConditionalDataBinding.Conditions) - if (Items.All(c => c.DataBindingCondition != condition)) - Items.Add(_dataBindingsVmFactory.DataBindingConditionViewModel(condition)); - - // Fix order - ((BindableCollection>) Items).Sort(c => c.DataBindingCondition.Order); - - _updating = false; - } - - private void Initialize() - { - ConditionalDataBinding.ConditionsUpdated += ConditionalDataBindingOnConditionsUpdated; - Items.CollectionChanged += ItemsOnCollectionChanged; - UpdateItems(); - } - - private void ItemsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - if (_updating || e.Action != NotifyCollectionChangedAction.Add) - return; - - for (int index = 0; index < Items.Count; index++) - { - DataBindingConditionViewModel conditionViewModel = Items[index]; - conditionViewModel.DataBindingCondition.Order = index + 1; - } - - ConditionalDataBinding.ApplyOrder(); - - _profileEditorService.SaveSelectedProfileElement(); - } - - private void ConditionalDataBindingOnConditionsUpdated(object sender, EventArgs e) - { - UpdateItems(); - } - - public bool SupportsTestValue => false; - - public void Update() - { - UpdateItems(); - } - - public object GetTestValue() - { - throw new NotSupportedException(); - } - - #region IDisposable - - /// - public void Dispose() - { - ConditionalDataBinding.ConditionsUpdated -= ConditionalDataBindingOnConditionsUpdated; - - foreach (DataBindingConditionViewModel conditionViewModel in Items) - conditionViewModel.Dispose(); - Items.Clear(); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionView.xaml deleted file mode 100644 index 6ae8e1eeb..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionView.xaml +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionViewModel.cs deleted file mode 100644 index e9f620f21..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionViewModel.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Artemis.Core; -using Artemis.Core.Modules; -using Artemis.UI.Ninject.Factories; -using Artemis.UI.Screens.ProfileEditor.Conditions; -using Artemis.UI.Shared; -using Artemis.UI.Shared.Input; -using Artemis.UI.Shared.Services; -using Stylet; - -namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.ConditionalDataBinding -{ - public sealed class DataBindingConditionViewModel : Conductor, IDisposable - { - private readonly IDataModelConditionsVmFactory _dataModelConditionsVmFactory; - private readonly IDataModelUIService _dataModelUIService; - private readonly IProfileEditorService _profileEditorService; - - public DataBindingConditionViewModel(DataBindingCondition dataBindingCondition, - IProfileEditorService profileEditorService, - IDataModelConditionsVmFactory dataModelConditionsVmFactory, - IDataModelUIService dataModelUIService) - { - _profileEditorService = profileEditorService; - _dataModelConditionsVmFactory = dataModelConditionsVmFactory; - _dataModelUIService = dataModelUIService; - DataBindingCondition = dataBindingCondition; - } - - public DataBindingCondition DataBindingCondition { get; } - - public DataModelStaticViewModel ValueViewModel { get; set; } - - protected override void OnInitialActivate() - { - base.OnInitialActivate(); - List modules = new(); - if (_profileEditorService.SelectedProfileConfiguration?.Module != null) - modules.Add(_profileEditorService.SelectedProfileConfiguration.Module); - ActiveItem = _dataModelConditionsVmFactory.DataModelConditionGroupViewModel(DataBindingCondition.Condition, ConditionGroupType.General, modules); - ActiveItem.IsRootGroup = true; - - ActiveItem.Update(); - ActiveItem.Updated += ActiveItemOnUpdated; - - ValueViewModel = _dataModelUIService.GetStaticInputViewModel(typeof(TProperty), null); - ValueViewModel.ValueUpdated += ValueViewModelOnValueUpdated; - ValueViewModel.Value = DataBindingCondition.Value; - } - - private void ActiveItemOnUpdated(object sender, EventArgs e) - { - if (!ActiveItem.GetChildren().Any()) - DataBindingCondition.ConditionalDataBinding.RemoveCondition(DataBindingCondition); - } - - private void ValueViewModelOnValueUpdated(object sender, DataModelInputStaticEventArgs e) - { - DataBindingCondition.Value = (TProperty) Convert.ChangeType(e.Value, typeof(TProperty)); - _profileEditorService.SaveSelectedProfileElement(); - } - - public void AddCondition() - { - ((ConditionalDataBindingModeViewModel) Parent).AddCondition(); - } - - public void RemoveCondition() - { - ((ConditionalDataBindingModeViewModel)Parent).RemoveCondition(DataBindingCondition); - - } - - #region IDisposable - - /// - public void Dispose() - { - ValueViewModel?.Dispose(); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml index 34fe61836..be5b8b20e 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml @@ -5,6 +5,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:s="https://github.com/canton7/Stylet" + xmlns:controls="clr-namespace:Artemis.VisualScripting.Editor.Controls;assembly=Artemis.VisualScripting" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> @@ -36,16 +37,8 @@ - - + Enable data binding + - - - + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs index 49b3040e9..9b0a82562 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs @@ -1,11 +1,10 @@ using System; +using System.Collections.Generic; using System.Linq; -using System.Timers; using Artemis.Core; using Artemis.Core.Services; using Artemis.Storage.Entities.Profile.DataBindings; using Artemis.UI.Exceptions; -using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline; using Artemis.UI.Shared; using Artemis.UI.Shared.Services; @@ -13,63 +12,62 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings { - public sealed class DataBindingViewModel : Conductor, IDataBindingViewModel + public sealed class DataBindingViewModel : Screen, IDataBindingViewModel { - private readonly IDataBindingsVmFactory _dataBindingsVmFactory; private readonly ICoreService _coreService; private readonly IProfileEditorService _profileEditorService; private int _easingTime; private bool _isDataBindingEnabled; private bool _isEasingTimeEnabled; - private DataBindingModeType _selectedDataBindingMode; + private DateTime _lastUpdate; private TimelineEasingViewModel _selectedEasingViewModel; private bool _updating; private bool _updatingTestResult; - private DateTime _lastUpdate; public DataBindingViewModel(DataBindingRegistration registration, ICoreService coreService, ISettingsService settingsService, IProfileEditorService profileEditorService, IDataModelUIService dataModelUIService, - IDataBindingsVmFactory dataBindingsVmFactory) + INodeService nodeService) { Registration = registration; _coreService = coreService; _profileEditorService = profileEditorService; - _dataBindingsVmFactory = dataBindingsVmFactory; DisplayName = Registration.DisplayName.ToUpper(); AlwaysApplyDataBindings = settingsService.GetSetting("ProfileEditor.AlwaysApplyDataBindings", true); - DataBindingModes = new BindableCollection(EnumUtilities.GetAllValuesAndDescriptions(typeof(DataBindingModeType))); EasingViewModels = new BindableCollection(); + AvailableNodes = nodeService.AvailableNodes; TestInputValue = dataModelUIService.GetDataModelDisplayViewModel(typeof(TProperty), null, true); TestResultValue = dataModelUIService.GetDataModelDisplayViewModel(typeof(TProperty), null, true); } public DataBindingRegistration Registration { get; } + public NodeScript Script => Registration.DataBinding?.Script; + public PluginSetting AlwaysApplyDataBindings { get; } - public BindableCollection DataBindingModes { get; } public BindableCollection EasingViewModels { get; } + public IEnumerable AvailableNodes { get; } public DataModelDisplayViewModel TestInputValue { get; } public DataModelDisplayViewModel TestResultValue { get; } - public DataBindingModeType SelectedDataBindingMode - { - get => _selectedDataBindingMode; - set - { - if (!SetAndNotify(ref _selectedDataBindingMode, value)) return; - ApplyDataBindingMode(); - Update(); - } - } public bool IsDataBindingEnabled { get => _isDataBindingEnabled; - set => SetAndNotify(ref _isDataBindingEnabled, value); + set + { + SetAndNotify(ref _isDataBindingEnabled, value); + if (_updating) + return; + + if (value) + EnableDataBinding(); + else + DisableDataBinding(); + } } public TimelineEasingViewModel SelectedEasingViewModel @@ -102,162 +100,6 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings } } - protected override void OnInitialActivate() - { - Initialize(); - base.OnInitialActivate(); - } - - private void Initialize() - { - EasingViewModels.AddRange(Enum.GetValues(typeof(Easings.Functions)).Cast().Select(v => new TimelineEasingViewModel(v, false))); - - _lastUpdate = DateTime.Now; - _coreService.FrameRendered += OnFrameRendered; - CreateDataBindingModeModeViewModel(); - Update(); - } - - private void CreateDataBindingModeModeViewModel() - { - if (Registration.DataBinding?.DataBindingMode == null) - { - ActiveItem = null; - return; - } - - switch (Registration.DataBinding.DataBindingMode) - { - case DirectDataBinding directDataBinding: - ActiveItem = _dataBindingsVmFactory.DirectDataBindingModeViewModel(directDataBinding); - break; - case ConditionalDataBinding conditionalDataBinding: - ActiveItem = _dataBindingsVmFactory.ConditionalDataBindingModeViewModel(conditionalDataBinding); - break; - } - } - - private void Update() - { - if (_updating) - return; - - if (Registration.DataBinding == null) - { - IsDataBindingEnabled = false; - IsEasingTimeEnabled = false; - return; - } - - _updating = true; - - IsDataBindingEnabled = ActiveItem != null; - EasingTime = (int) Registration.DataBinding.EasingTime.TotalMilliseconds; - SelectedEasingViewModel = EasingViewModels.First(vm => vm.EasingFunction == Registration.DataBinding.EasingFunction); - IsEasingTimeEnabled = EasingTime > 0; - SelectedDataBindingMode = Registration.DataBinding.DataBindingMode switch - { - DirectDataBinding _ => DataBindingModeType.Direct, - ConditionalDataBinding _ => DataBindingModeType.Conditional, - _ => DataBindingModeType.None - }; - - ActiveItem?.Update(); - - _updating = false; - } - - private void ApplyChanges() - { - if (_updating) - return; - - if (Registration.DataBinding != null) - { - Registration.DataBinding.EasingTime = TimeSpan.FromMilliseconds(EasingTime); - Registration.DataBinding.EasingFunction = SelectedEasingViewModel?.EasingFunction ?? Easings.Functions.Linear; - } - - _profileEditorService.SaveSelectedProfileElement(); - Update(); - } - - private void ApplyDataBindingMode() - { - if (_updating) - return; - - if (Registration.DataBinding != null && SelectedDataBindingMode == DataBindingModeType.None) - RemoveDataBinding(); - else - { - if (Registration.DataBinding == null && SelectedDataBindingMode != DataBindingModeType.None) - EnableDataBinding(); - - Registration.DataBinding!.ChangeDataBindingMode(SelectedDataBindingMode); - } - - CreateDataBindingModeModeViewModel(); - _profileEditorService.SaveSelectedProfileElement(); - } - - private void UpdateTestResult() - { - if (_updating || _updatingTestResult) - return; - - _updatingTestResult = true; - if (Registration.DataBinding == null || ActiveItem == null) - { - TestInputValue.UpdateValue(default); - TestResultValue.UpdateValue(default); - _updatingTestResult = false; - return; - } - - // If always update is disabled, do constantly update the data binding as long as the view model is open - // If always update is enabled, this is done for all data bindings in ProfileViewModel - if (!AlwaysApplyDataBindings.Value) - { - Registration.DataBinding.UpdateWithDelta(DateTime.Now - _lastUpdate); - _profileEditorService.UpdateProfilePreview(); - } - - if (ActiveItem.SupportsTestValue) - { - TProperty currentValue = Registration.Converter.ConvertFromObject(ActiveItem?.GetTestValue() ?? default(TProperty)); - TestInputValue.UpdateValue(currentValue); - TestResultValue.UpdateValue(Registration.DataBinding != null ? Registration.DataBinding.GetValue(currentValue) : default); - } - else - { - TestResultValue.UpdateValue(Registration.DataBinding != null ? Registration.DataBinding.GetValue(default) : default); - } - - _updatingTestResult = false; - } - - private void EnableDataBinding() - { - if (Registration.DataBinding != null) - return; - - Registration.LayerProperty.EnableDataBinding(Registration); - _profileEditorService.SaveSelectedProfileElement(); - } - - private void RemoveDataBinding() - { - if (Registration.DataBinding == null) - return; - - ActiveItem = null; - Registration.LayerProperty.DisableDataBinding(Registration.DataBinding); - Update(); - - _profileEditorService.SaveSelectedProfileElement(); - } - public void CopyDataBinding() { if (Registration.DataBinding != null) @@ -277,10 +119,115 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings Registration.DataBinding.EasingTime = dataBindingEntity.EasingTime; Registration.DataBinding.EasingFunction = (Easings.Functions) dataBindingEntity.EasingFunction; - Registration.DataBinding.ApplyDataBindingEntity(dataBindingEntity.DataBindingMode); - CreateDataBindingModeModeViewModel(); + + // TODO - Paste visual script + + Update(); + + _profileEditorService.SaveSelectedProfileElement(); + } + + protected override void OnInitialActivate() + { + Initialize(); + base.OnInitialActivate(); + } + + private void Initialize() + { + EasingViewModels.AddRange(Enum.GetValues(typeof(Easings.Functions)).Cast().Select(v => new TimelineEasingViewModel(v, false))); + + _lastUpdate = DateTime.Now; + _coreService.FrameRendered += OnFrameRendered; + Update(); + } + + private void Update() + { + if (_updating) + return; + + if (Registration.DataBinding == null) + { + IsDataBindingEnabled = false; + IsEasingTimeEnabled = false; + return; + } + + _updating = true; + + EasingTime = (int) Registration.DataBinding.EasingTime.TotalMilliseconds; + SelectedEasingViewModel = EasingViewModels.First(vm => vm.EasingFunction == Registration.DataBinding.EasingFunction); + IsDataBindingEnabled = true; + IsEasingTimeEnabled = EasingTime > 0; + + _updating = false; + } + + private void ApplyChanges() + { + if (_updating) + return; + + if (Registration.DataBinding != null) + { + Registration.DataBinding.EasingTime = TimeSpan.FromMilliseconds(EasingTime); + Registration.DataBinding.EasingFunction = SelectedEasingViewModel?.EasingFunction ?? Easings.Functions.Linear; + } + + _profileEditorService.SaveSelectedProfileElement(); + Update(); + } + + private void UpdateTestResult() + { + if (_updating || _updatingTestResult) + return; + + _updatingTestResult = true; + + if (Registration.DataBinding == null) + { + TestInputValue.UpdateValue(default); + TestResultValue.UpdateValue(default); + _updatingTestResult = false; + return; + } + + // If always update is disabled, do constantly update the data binding as long as the view model is open + // If always update is enabled, this is done for all data bindings in ProfileViewModel + if (!AlwaysApplyDataBindings.Value) + { + Registration.DataBinding.UpdateWithDelta(DateTime.Now - _lastUpdate); + _profileEditorService.UpdateProfilePreview(); + } + + TProperty currentValue = Registration.Converter.ConvertFromObject(Registration.DataBinding.Script.Result ?? default(TProperty)); + TestInputValue.UpdateValue(currentValue); + TestResultValue.UpdateValue(Registration.DataBinding != null ? Registration.DataBinding.GetValue(currentValue) : default); + + _updatingTestResult = false; + } + + private void EnableDataBinding() + { + if (Registration.DataBinding != null) + return; + + Registration.LayerProperty.EnableDataBinding(Registration); + NotifyOfPropertyChange(nameof(Script)); + + _profileEditorService.SaveSelectedProfileElement(); + } + + private void DisableDataBinding() + { + if (Registration.DataBinding == null) + return; + + Registration.LayerProperty.DisableDataBinding(Registration.DataBinding); + NotifyOfPropertyChange(nameof(Script)); Update(); - _profileEditorService.SaveSelectedProfileElement(); } @@ -291,20 +238,11 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings _lastUpdate = DateTime.Now; } - private void OnUpdateTimerOnElapsed(object sender, ElapsedEventArgs e) - { - UpdateTestResult(); - } - - #region IDisposable - /// public void Dispose() { _coreService.FrameRendered -= OnFrameRendered; } - - #endregion } public interface IDataBindingViewModel : IScreen, IDisposable diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DataBindingModifierView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DataBindingModifierView.xaml deleted file mode 100644 index 4d0635574..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DataBindingModifierView.xaml +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DataBindingModifierView.xaml.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DataBindingModifierView.xaml.cs deleted file mode 100644 index 86d341696..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DataBindingModifierView.xaml.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Windows; -using System.Windows.Controls; - -namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDataBinding -{ - /// - /// Interaction logic for DataBindingModifierView.xaml - /// - public partial class DataBindingModifierView : UserControl - { - public DataBindingModifierView() - { - InitializeComponent(); - } - - private void PropertyButton_OnClick(object sender, RoutedEventArgs e) - { - // DataContext is not set when using left button, I don't know why but there it is - if (sender is Button button && button.ContextMenu != null) - { - button.ContextMenu.DataContext = button.DataContext; - button.ContextMenu.IsOpen = true; - } - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DataBindingModifierViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DataBindingModifierViewModel.cs deleted file mode 100644 index f6811db6f..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DataBindingModifierViewModel.cs +++ /dev/null @@ -1,212 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Artemis.Core; -using Artemis.Core.Services; -using Artemis.UI.Exceptions; -using Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDataBinding.ModifierTypes; -using Artemis.UI.Shared; -using Artemis.UI.Shared.Input; -using Artemis.UI.Shared.Services; -using Stylet; - -namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDataBinding -{ - public sealed class DataBindingModifierViewModel : PropertyChangedBase, IDisposable - { - private readonly IDataBindingService _dataBindingService; - private readonly IDataModelUIService _dataModelUIService; - private readonly IProfileEditorService _profileEditorService; - private DataModelDynamicViewModel _dynamicSelectionViewModel; - private ModifierTypeCategoryViewModel _modifierTypeViewModels; - private BaseDataBindingModifierType _selectedModifierType; - private DataModelStaticViewModel _staticInputViewModel; - - public DataBindingModifierViewModel(DataBindingModifier modifier, - IDataBindingService dataBindingService, - ISettingsService settingsService, - IDataModelUIService dataModelUIService, - IProfileEditorService profileEditorService) - { - _dataBindingService = dataBindingService; - _dataModelUIService = dataModelUIService; - _profileEditorService = profileEditorService; - - ShowDataModelValues = settingsService.GetSetting("ProfileEditor.ShowDataModelValues"); - - Modifier = modifier; - SelectModifierTypeCommand = new DelegateCommand(ExecuteSelectModifierTypeCommand); - - Update(); - } - - public DelegateCommand SelectModifierTypeCommand { get; } - public PluginSetting ShowDataModelValues { get; } - public DataBindingModifier Modifier { get; } - - public ModifierTypeCategoryViewModel ModifierTypeViewModels - { - get => _modifierTypeViewModels; - set => SetAndNotify(ref _modifierTypeViewModels, value); - } - - public BaseDataBindingModifierType SelectedModifierType - { - get => _selectedModifierType; - set => SetAndNotify(ref _selectedModifierType, value); - } - - public DataModelDynamicViewModel DynamicSelectionViewModel - { - get => _dynamicSelectionViewModel; - private set => SetAndNotify(ref _dynamicSelectionViewModel, value); - } - - public DataModelStaticViewModel StaticInputViewModel - { - get => _staticInputViewModel; - private set => SetAndNotify(ref _staticInputViewModel, value); - } - - public void Delete() - { - Modifier.DirectDataBinding.RemoveModifier(Modifier); - _profileEditorService.SaveSelectedProfileElement(); - } - - private void ParameterSelectionViewModelOnPropertySelected(object sender, DataModelInputDynamicEventArgs e) - { - Modifier.UpdateParameterDynamic(e.DataModelPath); - _profileEditorService.SaveSelectedProfileElement(); - } - - private void StaticInputViewModelOnValueUpdated(object sender, DataModelInputStaticEventArgs e) - { - Modifier.UpdateParameterStatic(e.Value); - _profileEditorService.SaveSelectedProfileElement(); - } - - private void Update() - { - Type sourceType = Modifier.DirectDataBinding.GetSourceType(); - if (sourceType == null) - throw new ArtemisUIException("Cannot use a data binding modifier VM for a data binding without a source"); - - if (Modifier.ModifierType == null || Modifier.ModifierType.ParameterType == null) - { - DisposeDynamicSelectionViewModel(); - DisposeStaticInputViewModel(); - } - else if (Modifier.ParameterType == ProfileRightSideType.Dynamic) - { - DisposeStaticInputViewModel(); - DynamicSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_profileEditorService.SelectedProfileConfiguration.Module); - if (DynamicSelectionViewModel != null) - { - DynamicSelectionViewModel.DisplaySwitchButton = true; - DynamicSelectionViewModel.PropertySelected += ParameterSelectionViewModelOnPropertySelected; - DynamicSelectionViewModel.SwitchToStaticRequested += DynamicSelectionViewModelOnSwitchToStaticRequested; - DynamicSelectionViewModel.FilterTypes = new[] {Modifier.ModifierType.ParameterType ?? sourceType}; - } - } - else - { - DisposeDynamicSelectionViewModel(); - StaticInputViewModel = _dataModelUIService.GetStaticInputViewModel(Modifier.ModifierType.ParameterType ?? sourceType, null); - if (StaticInputViewModel != null) - { - StaticInputViewModel.DisplaySwitchButton = true; - StaticInputViewModel.ValueUpdated += StaticInputViewModelOnValueUpdated; - StaticInputViewModel.SwitchToDynamicRequested += StaticInputViewModelOnSwitchToDynamicRequested; - } - } - - // Modifier type - ModifierTypeCategoryViewModel root = new(null, null); - IEnumerable> modifierTypes = _dataBindingService.GetCompatibleModifierTypes(sourceType, ModifierTypePart.Value).GroupBy(t => t.Category); - foreach (IGrouping dataBindingModifierTypes in modifierTypes) - { - IEnumerable viewModels = dataBindingModifierTypes.Select(t => new ModifierTypeViewModel(t)); - if (dataBindingModifierTypes.Key == null) - root.Children.AddRange(viewModels); - else - root.Children.Add(new ModifierTypeCategoryViewModel(dataBindingModifierTypes.Key, viewModels)); - } - - ModifierTypeViewModels = root; - SelectedModifierType = Modifier.ModifierType; - - // Parameter - if (DynamicSelectionViewModel != null) - DynamicSelectionViewModel.ChangeDataModelPath(Modifier.ParameterPath); - else if (StaticInputViewModel != null) - StaticInputViewModel.Value = Modifier.ParameterStaticValue; - } - - private void ExecuteSelectModifierTypeCommand(object context) - { - if (!(context is ModifierTypeViewModel modifierTypeViewModel)) - return; - - Modifier.UpdateModifierType(modifierTypeViewModel.ModifierType); - _profileEditorService.SaveSelectedProfileElement(); - - Update(); - } - - #region IDisposable - - /// - public void Dispose() - { - DisposeDynamicSelectionViewModel(); - DisposeStaticInputViewModel(); - } - - private void DisposeStaticInputViewModel() - { - if (StaticInputViewModel != null) - { - StaticInputViewModel.Dispose(); - StaticInputViewModel.ValueUpdated -= StaticInputViewModelOnValueUpdated; - StaticInputViewModel.SwitchToDynamicRequested -= StaticInputViewModelOnSwitchToDynamicRequested; - StaticInputViewModel = null; - } - } - - private void DisposeDynamicSelectionViewModel() - { - if (DynamicSelectionViewModel != null) - { - DynamicSelectionViewModel.Dispose(); - DynamicSelectionViewModel.PropertySelected -= ParameterSelectionViewModelOnPropertySelected; - DynamicSelectionViewModel.SwitchToStaticRequested -= DynamicSelectionViewModelOnSwitchToStaticRequested; - DynamicSelectionViewModel = null; - } - } - - #endregion - - #region Event handlers - - private void DynamicSelectionViewModelOnSwitchToStaticRequested(object sender, EventArgs e) - { - Modifier.ParameterType = ProfileRightSideType.Static; - - // Ensure the right static value is never null when the preferred type is a value type - if (SelectedModifierType.ParameterType != null && - SelectedModifierType.ParameterType.IsValueType && Modifier.ParameterStaticValue == null) - Modifier.UpdateParameterStatic(SelectedModifierType.ParameterType.GetDefault()); - - Update(); - } - - private void StaticInputViewModelOnSwitchToDynamicRequested(object sender, EventArgs e) - { - Modifier.ParameterType = ProfileRightSideType.Dynamic; - Update(); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeView.xaml deleted file mode 100644 index d33ce960e..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeView.xaml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeViewModel.cs deleted file mode 100644 index 40479a351..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeViewModel.cs +++ /dev/null @@ -1,169 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Linq; -using Artemis.Core; -using Artemis.Core.Modules; -using Artemis.UI.Extensions; -using Artemis.UI.Ninject.Factories; -using Artemis.UI.Shared; -using Artemis.UI.Shared.Input; -using Artemis.UI.Shared.Services; -using Stylet; - -namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDataBinding -{ - public sealed class DirectDataBindingModeViewModel : Screen, IDataBindingModeViewModel - { - private readonly IDataBindingsVmFactory _dataBindingsVmFactory; - private readonly IDataModelUIService _dataModelUIService; - private readonly IProfileEditorService _profileEditorService; - private bool _canAddModifier; - private DataModelDynamicViewModel _targetSelectionViewModel; - private bool _updating; - - public DirectDataBindingModeViewModel(DirectDataBinding directDataBinding, - IProfileEditorService profileEditorService, - IDataModelUIService dataModelUIService, - IDataBindingsVmFactory dataBindingsVmFactory) - { - _profileEditorService = profileEditorService; - _dataModelUIService = dataModelUIService; - _dataBindingsVmFactory = dataBindingsVmFactory; - - DirectDataBinding = directDataBinding; - ModifierViewModels = new BindableCollection>(); - - Initialize(); - } - - - public DirectDataBinding DirectDataBinding { get; } - public BindableCollection> ModifierViewModels { get; } - - public bool CanAddModifier - { - get => _canAddModifier; - set => SetAndNotify(ref _canAddModifier, value); - } - - public DataModelDynamicViewModel TargetSelectionViewModel - { - get => _targetSelectionViewModel; - private set => SetAndNotify(ref _targetSelectionViewModel, value); - } - - public bool SupportsTestValue => true; - - public void Update() - { - TargetSelectionViewModel.ChangeDataModelPath(DirectDataBinding.SourcePath); - TargetSelectionViewModel.FilterTypes = new[] {DirectDataBinding.DataBinding.GetTargetType()}; - - CanAddModifier = DirectDataBinding.SourcePath != null && DirectDataBinding.SourcePath.IsValid; - UpdateModifierViewModels(); - } - - public object GetTestValue() - { - return DirectDataBinding.SourcePath?.GetValue(); - } - - #region IDisposable - - /// - public void Dispose() - { - TargetSelectionViewModel.PropertySelected -= TargetSelectionViewModelOnPropertySelected; - TargetSelectionViewModel.Dispose(); - DirectDataBinding.ModifiersUpdated -= DirectDataBindingOnModifiersUpdated; - - foreach (DataBindingModifierViewModel dataBindingModifierViewModel in ModifierViewModels) - dataBindingModifierViewModel.Dispose(); - ModifierViewModels.Clear(); - } - - #endregion - - private void Initialize() - { - DirectDataBinding.ModifiersUpdated += DirectDataBindingOnModifiersUpdated; - TargetSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_profileEditorService.SelectedProfileConfiguration.Module); - TargetSelectionViewModel.PropertySelected += TargetSelectionViewModelOnPropertySelected; - ModifierViewModels.CollectionChanged += ModifierViewModelsOnCollectionChanged; - Update(); - } - - private void ModifierViewModelsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - if (_updating || e.Action != NotifyCollectionChangedAction.Add) - return; - - for (int index = 0; index < ModifierViewModels.Count; index++) - { - DataBindingModifierViewModel dataBindingModifierViewModel = ModifierViewModels[index]; - dataBindingModifierViewModel.Modifier.Order = index + 1; - } - - DirectDataBinding.ApplyOrder(); - - _profileEditorService.SaveSelectedProfileElement(); - } - - #region Target - - private void TargetSelectionViewModelOnPropertySelected(object sender, DataModelInputDynamicEventArgs e) - { - DirectDataBinding.UpdateSource(e.DataModelPath); - Update(); - - _profileEditorService.SaveSelectedProfileElement(); - } - - #endregion - - #region Modifiers - - public void AddModifier() - { - DirectDataBinding.AddModifier(ProfileRightSideType.Dynamic); - _profileEditorService.SaveSelectedProfileElement(); - } - - private void UpdateModifierViewModels() - { - _updating = true; - - // Remove old VMs - List> toRemove = ModifierViewModels.Where(m => !DirectDataBinding.Modifiers.Contains(m.Modifier)).ToList(); - foreach (DataBindingModifierViewModel modifierViewModel in toRemove) - { - ModifierViewModels.Remove(modifierViewModel); - modifierViewModel.Dispose(); - } - - // Don't create children without a source or with an invalid source - if (DirectDataBinding.SourcePath == null || !DirectDataBinding.SourcePath.IsValid) - return; - - // Add missing VMs - foreach (DataBindingModifier modifier in DirectDataBinding.Modifiers) - { - if (ModifierViewModels.All(m => m.Modifier != modifier)) - ModifierViewModels.Add(_dataBindingsVmFactory.DataBindingModifierViewModel(modifier)); - } - - // Fix order - ModifierViewModels.Sort(m => m.Modifier.Order); - - _updating = false; - } - - private void DirectDataBindingOnModifiersUpdated(object sender, EventArgs e) - { - UpdateModifierViewModels(); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/ModifierTypes/IModifierTypeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/ModifierTypes/IModifierTypeViewModel.cs deleted file mode 100644 index baa398a12..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/ModifierTypes/IModifierTypeViewModel.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDataBinding.ModifierTypes -{ - public interface IModifierTypeViewModel - { - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/ModifierTypes/ModifierTypeCategoryViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/ModifierTypes/ModifierTypeCategoryViewModel.cs deleted file mode 100644 index e1d5e3966..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/ModifierTypes/ModifierTypeCategoryViewModel.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Collections.Generic; -using Stylet; - -namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDataBinding.ModifierTypes -{ - public class ModifierTypeCategoryViewModel : IModifierTypeViewModel - { - public ModifierTypeCategoryViewModel(string category, IEnumerable children) - { - Category = category; - Children = children == null - ? new BindableCollection() - : new BindableCollection(children); - } - - public string Category { get; set; } - public BindableCollection Children { get; set; } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/ModifierTypes/ModifierTypeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/ModifierTypes/ModifierTypeViewModel.cs deleted file mode 100644 index af7eb7daf..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/ModifierTypes/ModifierTypeViewModel.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Artemis.Core; - -namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDataBinding.ModifierTypes -{ - public class ModifierTypeViewModel : IModifierTypeViewModel - { - public ModifierTypeViewModel(BaseDataBindingModifierType modifierType) - { - ModifierType = modifierType; - } - - public BaseDataBindingModifierType ModifierType { get; set; } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/IDataBindingModeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/IDataBindingModeViewModel.cs deleted file mode 100644 index 09db1fe3f..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/IDataBindingModeViewModel.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using Stylet; - -namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings -{ - public interface IDataBindingModeViewModel : IScreen, IDisposable - { - bool SupportsTestValue { get; } - void Update(); - object GetTestValue(); - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileEdit/ProfileEditViewModel.cs b/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileEdit/ProfileEditViewModel.cs index bbfb771cd..dc27af8a1 100644 --- a/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileEdit/ProfileEditViewModel.cs +++ b/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileEdit/ProfileEditViewModel.cs @@ -3,14 +3,10 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; -using System.Windows; -using System.Windows.Media; -using System.Windows.Media.Imaging; using Artemis.Core; using Artemis.Core.Modules; using Artemis.Core.Services; using Artemis.UI.Ninject.Factories; -using Artemis.UI.Screens.ProfileEditor.Conditions; using Artemis.UI.Shared; using Artemis.UI.Shared.Services; using FluentValidation; @@ -22,7 +18,6 @@ namespace Artemis.UI.Screens.Sidebar.Dialogs.ProfileEdit { public class ProfileEditViewModel : DialogViewModelBase { - private readonly DataModelConditionGroup _dataModelConditionGroup; private readonly List _modules; private readonly IProfileService _profileService; private bool _changedImage; @@ -37,14 +32,12 @@ namespace Artemis.UI.Screens.Sidebar.Dialogs.ProfileEdit IProfileService profileService, IPluginManagementService pluginManagementService, ISidebarVmFactory sidebarVmFactory, - IDataModelConditionsVmFactory dataModelConditionsVmFactory, IModelValidator validator) : base(validator) { ProfileConfiguration = profileConfiguration; IsNew = isNew; _profileService = profileService; - _dataModelConditionGroup = ProfileConfiguration.ActivationCondition ?? new DataModelConditionGroup(null); _modules = ProfileConfiguration.Module != null ? new List {ProfileConfiguration.Module} : new List(); IconTypes = new BindableCollection(EnumUtilities.GetAllValuesAndDescriptions(typeof(ProfileConfigurationIconType))); @@ -54,10 +47,7 @@ namespace Artemis.UI.Screens.Sidebar.Dialogs.ProfileEdit pluginManagementService.GetFeaturesOfType().Where(m => !m.IsAlwaysAvailable).Select(m => new ProfileModuleViewModel(m)) ); Initializing = true; - - ActivationConditionViewModel = dataModelConditionsVmFactory.DataModelConditionGroupViewModel(_dataModelConditionGroup, ConditionGroupType.General, _modules); - ActivationConditionViewModel.ConductWith(this); - ActivationConditionViewModel.IsRootGroup = true; + ModuleActivationRequirementsViewModel = new ModuleActivationRequirementsViewModel(sidebarVmFactory); ModuleActivationRequirementsViewModel.ConductWith(this); ModuleActivationRequirementsViewModel.SetModule(ProfileConfiguration.Module); @@ -84,8 +74,7 @@ namespace Artemis.UI.Screens.Sidebar.Dialogs.ProfileEdit Initializing = false; }); } - - public DataModelConditionGroupViewModel ActivationConditionViewModel { get; } + public ModuleActivationRequirementsViewModel ModuleActivationRequirementsViewModel { get; } public ProfileConfigurationHotkeyViewModel EnableHotkeyViewModel { get; } public ProfileConfigurationHotkeyViewModel DisableHotkeyViewModel { get; } @@ -160,7 +149,6 @@ namespace Artemis.UI.Screens.Sidebar.Dialogs.ProfileEdit if (value != null) _modules.Add(value.Module); - ActivationConditionViewModel.UpdateModules(); ModuleActivationRequirementsViewModel.SetModule(value?.Module); } } @@ -184,9 +172,6 @@ namespace Artemis.UI.Screens.Sidebar.Dialogs.ProfileEdit ProfileConfiguration.Module = SelectedModule?.Module; - if (_dataModelConditionGroup.Children.Any()) - ProfileConfiguration.ActivationCondition = _dataModelConditionGroup; - if (_changedImage) { ProfileConfiguration.Icon.FileIcon = SelectedImage; diff --git a/src/Artemis.VisualScripting/Artemis.VisualScripting.csproj b/src/Artemis.VisualScripting/Artemis.VisualScripting.csproj index bf5de01e7..93f3b3450 100644 --- a/src/Artemis.VisualScripting/Artemis.VisualScripting.csproj +++ b/src/Artemis.VisualScripting/Artemis.VisualScripting.csproj @@ -50,5 +50,8 @@ $(DefaultXamlRuntime) Designer + + $(DefaultXamlRuntime) + \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptCablePresenter.cs b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptCablePresenter.cs index 4487ba42c..85f6a8a7c 100644 --- a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptCablePresenter.cs +++ b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptCablePresenter.cs @@ -1,9 +1,12 @@ using System; +using System.Collections; using System.ComponentModel; using System.Windows; using System.Windows.Controls; using System.Windows.Input; +using System.Windows.Media; using System.Windows.Shapes; +using Artemis.Core; using Artemis.VisualScripting.Editor.Controls.Wrapper; namespace Artemis.VisualScripting.Editor.Controls @@ -48,7 +51,7 @@ namespace Artemis.VisualScripting.Editor.Controls public Point ValuePosition { - get => (Point) GetValue(ValuePositionProperty); + get => (Point)GetValue(ValuePositionProperty); set => SetValue(ValuePositionProperty, value); } @@ -60,7 +63,7 @@ namespace Artemis.VisualScripting.Editor.Controls { _path = GetTemplateChild(PART_PATH) as Path ?? throw new NullReferenceException($"The Path '{PART_PATH}' is missing."); _path.MouseDown += OnPathMouseDown; - + Unloaded += OnUnloaded; } @@ -104,25 +107,31 @@ namespace Artemis.VisualScripting.Editor.Controls newCable.To.PropertyChanged += OnPinPropertyChanged; } + UpdateBorderBrush(); UpdateValuePosition(); } - + private void OnPinPropertyChanged(object sender, PropertyChangedEventArgs e) { - UpdateValuePosition(); + if (e.PropertyName == nameof(VisualScriptPin.AbsolutePosition)) + UpdateValuePosition(); + } + + private void UpdateBorderBrush() + { + // if (Cable.From.Pin.Type.IsAssignableTo(typeof(IList))) + // BorderBrush = new SolidColorBrush(Colors.MediumPurple); } private void UpdateValuePosition() { if (Cable.From == null || Cable.To == null) - ValuePosition = new Point(); - else - { - ValuePosition = new Point( - Cable.From.AbsolutePosition.X + ((Cable.To.AbsolutePosition.X - Cable.From.AbsolutePosition.X) / 2), - Cable.From.AbsolutePosition.Y + ((Cable.To.AbsolutePosition.Y - Cable.From.AbsolutePosition.Y) / 2) - ); - } + return; + + ValuePosition = new Point( + Cable.From.AbsolutePosition.X + ((Cable.To.AbsolutePosition.X - Cable.From.AbsolutePosition.X) / 2), + Cable.From.AbsolutePosition.Y + ((Cable.To.AbsolutePosition.Y - Cable.From.AbsolutePosition.Y) / 2) + ); } #endregion diff --git a/src/Artemis.VisualScripting/Editor/Styles/VisualScriptCablePresenter.xaml b/src/Artemis.VisualScripting/Editor/Styles/VisualScriptCablePresenter.xaml index e5cb83c06..0a507021d 100644 --- a/src/Artemis.VisualScripting/Editor/Styles/VisualScriptCablePresenter.xaml +++ b/src/Artemis.VisualScripting/Editor/Styles/VisualScriptCablePresenter.xaml @@ -2,7 +2,8 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:system="clr-namespace:System;assembly=System.Runtime" xmlns:controls="clr-namespace:Artemis.VisualScripting.Editor.Controls" - xmlns:converters="clr-namespace:Artemis.VisualScripting.Converters"> + xmlns:converters="clr-namespace:Artemis.VisualScripting.Converters" + xmlns:collections="clr-namespace:System.Collections;assembly=System.Runtime"> + + + + diff --git a/src/Artemis.VisualScripting/Editor/Styles/VisualScriptPresenter.xaml b/src/Artemis.VisualScripting/Editor/Styles/VisualScriptPresenter.xaml index 506619a99..afa908aa7 100644 --- a/src/Artemis.VisualScripting/Editor/Styles/VisualScriptPresenter.xaml +++ b/src/Artemis.VisualScripting/Editor/Styles/VisualScriptPresenter.xaml @@ -7,14 +7,14 @@ - @@ -27,14 +27,14 @@ Height="{Binding SurfaceSize, RelativeSource={RelativeSource TemplatedParent}}" Background="{TemplateBinding Background}"> - - @@ -50,10 +50,10 @@ @@ -79,7 +79,7 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.sln.DotSettings b/src/Artemis.sln.DotSettings index c899abc86..7af04acad 100644 --- a/src/Artemis.sln.DotSettings +++ b/src/Artemis.sln.DotSettings @@ -2,6 +2,7 @@ Inherit True True + True ERROR ERROR Built-in: Full Cleanup From b8077ca589ea115018d9cab891f672f83bced47f Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 19 Aug 2021 11:40:50 +0200 Subject: [PATCH 12/47] Nodes - Added type color registration system Nodes - Generate (stable) type colors for undefined types --- .../Extensions/SKColorExtensions.cs | 17 ++- src/Artemis.Core/Services/NodeService.cs | 50 +++++-- src/Artemis.Core/Stores/NodeTypeStore.cs | 61 ++++++-- .../Registrations/NodeTypeRegistration.cs | 55 ++++++- .../Services/ProfileEditorService.cs | 136 +++++++++--------- .../Utilities/TypeUtilities.cs | 43 ++++++ .../DisplayConditionsViewModel.cs | 6 +- .../Services/RegistrationService.cs | 9 ++ .../Controls/VisualScriptCablePresenter.cs | 13 +- .../Controls/VisualScriptPinPresenter.cs | 9 ++ .../Styles/VisualScriptCablePresenter.xaml | 20 --- .../Styles/VisualScriptPinPresenter.xaml | 25 ---- 12 files changed, 297 insertions(+), 147 deletions(-) create mode 100644 src/Artemis.UI.Shared/Utilities/TypeUtilities.cs diff --git a/src/Artemis.Core/Extensions/SKColorExtensions.cs b/src/Artemis.Core/Extensions/SKColorExtensions.cs index dbc14239f..f1e9297ec 100644 --- a/src/Artemis.Core/Extensions/SKColorExtensions.cs +++ b/src/Artemis.Core/Extensions/SKColorExtensions.cs @@ -16,7 +16,7 @@ namespace Artemis.Core /// The RGB.NET color public static Color ToRgbColor(this SKColor color) { - return new(color.Alpha, color.Red, color.Green, color.Blue); + return new Color(color.Alpha, color.Red, color.Green, color.Blue); } /// @@ -49,7 +49,7 @@ namespace Artemis.Core /// The sum of the two colors public static SKColor Sum(this SKColor a, SKColor b) { - return new( + return new SKColor( ClampToByte(a.Red + b.Red), ClampToByte(a.Green + b.Green), ClampToByte(a.Blue + b.Blue), @@ -57,6 +57,19 @@ namespace Artemis.Core ); } + /// + /// Darkens the color by the specified amount + /// + /// The color to darken + /// The brightness of the new color + /// The darkened color + public static SKColor Darken(this SKColor c, float amount) + { + c.ToHsl(out float h, out float s, out float l); + l *= 1f - amount; + return SKColor.FromHsl(h, s, l); + } + private static byte ClampToByte(float value) { return (byte) Math.Clamp(value, 0, 255); diff --git a/src/Artemis.Core/Services/NodeService.cs b/src/Artemis.Core/Services/NodeService.cs index 74d856f51..66ffd7804 100644 --- a/src/Artemis.Core/Services/NodeService.cs +++ b/src/Artemis.Core/Services/NodeService.cs @@ -4,24 +4,19 @@ using System.Reflection; using Artemis.Storage.Entities.Profile.Nodes; using Ninject; using Ninject.Parameters; +using SkiaSharp; namespace Artemis.Core.Services { internal class NodeService : INodeService { - private readonly IKernel _kernel; - #region Constants private static readonly Type TYPE_NODE = typeof(INode); #endregion - #region Properties & Fields - - public IEnumerable AvailableNodes => NodeTypeStore.GetAll(); - - #endregion + private readonly IKernel _kernel; #region Constructors @@ -32,8 +27,20 @@ namespace Artemis.Core.Services #endregion + #region Properties & Fields + + public IEnumerable AvailableNodes => NodeTypeStore.GetAll(); + + #endregion + #region Methods + /// + public TypeColorRegistration? GetTypeColor(Type type) + { + return NodeTypeStore.GetColor(type); + } + public NodeTypeRegistration RegisterNodeType(Plugin plugin, Type nodeType) { if (plugin == null) throw new ArgumentNullException(nameof(plugin)); @@ -46,10 +53,18 @@ namespace Artemis.Core.Services string description = nodeAttribute?.Description ?? string.Empty; string category = nodeAttribute?.Category ?? string.Empty; - NodeData nodeData = new(plugin, nodeType, name, description, category, (e) => CreateNode(e, nodeType)); + NodeData nodeData = new(plugin, nodeType, name, description, category, e => CreateNode(e, nodeType)); return NodeTypeStore.Add(nodeData); } + public TypeColorRegistration RegisterTypeColor(Plugin plugin, Type type, SKColor color) + { + if (plugin == null) throw new ArgumentNullException(nameof(plugin)); + if (type == null) throw new ArgumentNullException(nameof(type)); + + return NodeTypeStore.AddColor(type, color, plugin); + } + private INode CreateNode(NodeEntity? entity, Type nodeType) { INode node = _kernel.Get(nodeType) as INode ?? throw new InvalidOperationException($"Node {nodeType} is not an INode"); @@ -72,20 +87,33 @@ namespace Artemis.Core.Services } /// - /// A service that provides access to the node system + /// A service that provides access to the node system /// public interface INodeService : IArtemisService { /// - /// Gets all available nodes + /// Gets all available nodes /// IEnumerable AvailableNodes { get; } /// - /// Initializes a node of the provided + /// Gets the best matching registration for the provided type + /// + TypeColorRegistration? GetTypeColor(Type type); + + /// + /// Registers a node of the provided /// /// The plugin the node belongs to /// The type of node to initialize NodeTypeRegistration RegisterNodeType(Plugin plugin, Type nodeType); + + /// + /// Registers a type with a provided color for use in the node editor + /// + /// The plugin making the registration + /// The type to associate the color with + /// The color to display + TypeColorRegistration RegisterTypeColor(Plugin plugin, Type type, SKColor color); } } \ No newline at end of file diff --git a/src/Artemis.Core/Stores/NodeTypeStore.cs b/src/Artemis.Core/Stores/NodeTypeStore.cs index 705a4d223..aee2896e3 100644 --- a/src/Artemis.Core/Stores/NodeTypeStore.cs +++ b/src/Artemis.Core/Stores/NodeTypeStore.cs @@ -1,12 +1,14 @@ using System; using System.Collections.Generic; using System.Linq; +using SkiaSharp; namespace Artemis.Core { internal class NodeTypeStore { private static readonly List Registrations = new(); + private static readonly List ColorRegistrations = new(); public static NodeTypeRegistration Add(NodeData nodeData) { @@ -19,7 +21,7 @@ namespace Artemis.Core if (Registrations.Any(r => r.NodeData == nodeData)) throw new ArtemisCoreException($"Data binding modifier type store already contains modifier '{nodeData.Name}'"); - typeRegistration = new NodeTypeRegistration(nodeData, nodeData.Plugin) { IsInStore = true }; + typeRegistration = new NodeTypeRegistration(nodeData, nodeData.Plugin) {IsInStore = true}; Registrations.Add(typeRegistration); } @@ -32,7 +34,7 @@ namespace Artemis.Core lock (Registrations) { if (!Registrations.Contains(typeRegistration)) - throw new ArtemisCoreException($"Data binding modifier type store does not contain modifier type '{typeRegistration.NodeData.Name}'"); + throw new ArtemisCoreException($"Node type store does not contain modifier type '{typeRegistration.NodeData.Name}'"); Registrations.Remove(typeRegistration); typeRegistration.IsInStore = false; @@ -57,6 +59,51 @@ namespace Artemis.Core } } + public static Plugin? GetPlugin(INode node) + { + lock (Registrations) + { + return Registrations.FirstOrDefault(r => r.Plugin.GetType().Assembly == node.GetType().Assembly)?.Plugin; + } + } + + public static TypeColorRegistration AddColor(Type type, SKColor color, Plugin plugin) + { + TypeColorRegistration typeColorRegistration; + lock (ColorRegistrations) + { + if (ColorRegistrations.Any(r => r.Type == type)) + throw new ArtemisCoreException($"Node color store already contains a color for '{type.Name}'"); + + typeColorRegistration = new TypeColorRegistration(type, color, plugin) {IsInStore = true}; + ColorRegistrations.Add(typeColorRegistration); + } + + return typeColorRegistration; + } + + public static void RemoveColor(TypeColorRegistration typeColorRegistration) + { + lock (ColorRegistrations) + { + if (!ColorRegistrations.Contains(typeColorRegistration)) + throw new ArtemisCoreException($"Node color store does not contain modifier type '{typeColorRegistration.Type.Name}'"); + + ColorRegistrations.Remove(typeColorRegistration); + typeColorRegistration.IsInStore = false; + } + } + + public static TypeColorRegistration? GetColor(Type type) + { + lock (ColorRegistrations) + { + return ColorRegistrations + .OrderByDescending(r => r.Type.ScoreCastability(type)) + .FirstOrDefault(r => r.Type.ScoreCastability(type) > 0); + } + } + #region Events public static event EventHandler? NodeTypeAdded; @@ -73,13 +120,5 @@ namespace Artemis.Core } #endregion - - public static Plugin? GetPlugin(INode node) - { - lock (Registrations) - { - return Registrations.FirstOrDefault(r => r.Plugin.GetType().Assembly == node.GetType().Assembly)?.Plugin; - } - } } -} +} \ No newline at end of file diff --git a/src/Artemis.Core/Stores/Registrations/NodeTypeRegistration.cs b/src/Artemis.Core/Stores/Registrations/NodeTypeRegistration.cs index a9b8e4873..799bee80e 100644 --- a/src/Artemis.Core/Stores/Registrations/NodeTypeRegistration.cs +++ b/src/Artemis.Core/Stores/Registrations/NodeTypeRegistration.cs @@ -1,9 +1,10 @@ using System; +using SkiaSharp; namespace Artemis.Core { /// - /// Represents a registration for a type of + /// Represents a registration for a type of /// public class NodeTypeRegistration { @@ -15,13 +16,16 @@ namespace Artemis.Core Plugin.Disabled += OnDisabled; } + /// + /// Gets the node data that was registered + /// public NodeData NodeData { get; } /// /// Gets the plugin the node is associated with /// public Plugin Plugin { get; } - + /// /// Gets a boolean indicating whether the registration is in the internal Core store /// @@ -34,4 +38,51 @@ namespace Artemis.Core NodeTypeStore.Remove(this); } } + + /// + /// Represents a registration for a to associate with a certain + /// + public class TypeColorRegistration + { + internal TypeColorRegistration(Type type, SKColor color, Plugin plugin) + { + Type = type; + Color = color; + Plugin = plugin; + + Plugin.Disabled += OnDisabled; + } + + /// + /// Gets the type + /// + public Type Type { get; } + + /// + /// Gets the color + /// + public SKColor Color { get; } + + /// + /// Gets a darkened tone of the + /// + public SKColor DarkenedColor => Color.Darken(0.35f); + + /// + /// Gets the plugin type color is associated with + /// + public Plugin Plugin { get; } + + /// + /// Gets a boolean indicating whether the registration is in the internal Core store + /// + public bool IsInStore { get; internal set; } + + private void OnDisabled(object? sender, EventArgs e) + { + Plugin.Disabled -= OnDisabled; + if (IsInStore) + NodeTypeStore.RemoveColor(this); + } + } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs index f3f3626ea..37cccd627 100644 --- a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs @@ -20,10 +20,10 @@ namespace Artemis.UI.Shared.Services { private readonly IKernel _kernel; private readonly ILogger _logger; + private readonly IModuleService _moduleService; private readonly IProfileService _profileService; private readonly List _registeredPropertyEditors; private readonly IRgbService _rgbService; - private readonly IModuleService _moduleService; private readonly object _selectedProfileElementLock = new(); private readonly object _selectedProfileLock = new(); private TimeSpan _currentTime; @@ -31,7 +31,8 @@ namespace Artemis.UI.Shared.Services private int _pixelsPerSecond; private bool _suspendEditing; - public ProfileEditorService(IKernel kernel, ILogger logger, IProfileService profileService, ICoreService coreService, IRgbService rgbService, IModuleService moduleService) + public ProfileEditorService(IKernel kernel, ILogger logger, ICoreService coreService, IProfileService profileService, IRgbService rgbService, IModuleService moduleService, + INodeService nodeService) { _kernel = kernel; _logger = logger; @@ -40,9 +41,56 @@ namespace Artemis.UI.Shared.Services _moduleService = moduleService; _registeredPropertyEditors = new List(); coreService.FrameRendered += CoreServiceOnFrameRendered; + + TypeUtilities.NodeService = nodeService; PixelsPerSecond = 100; } + protected virtual void OnSelectedProfileChanged(ProfileConfigurationEventArgs e) + { + SelectedProfileChanged?.Invoke(this, e); + } + + protected virtual void OnSelectedProfileUpdated(ProfileConfigurationEventArgs e) + { + SelectedProfileSaved?.Invoke(this, e); + } + + protected virtual void OnSelectedProfileElementChanged(RenderProfileElementEventArgs e) + { + SelectedProfileElementChanged?.Invoke(this, e); + } + + protected virtual void OnSelectedProfileElementUpdated(RenderProfileElementEventArgs e) + { + SelectedProfileElementSaved?.Invoke(this, e); + } + + protected virtual void OnCurrentTimeChanged() + { + CurrentTimeChanged?.Invoke(this, EventArgs.Empty); + } + + protected virtual void OnPixelsPerSecondChanged() + { + PixelsPerSecondChanged?.Invoke(this, EventArgs.Empty); + } + + protected virtual void OnSuspendEditingChanged() + { + SuspendEditingChanged?.Invoke(this, EventArgs.Empty); + } + + protected virtual void OnProfilePreviewUpdated() + { + ProfilePreviewUpdated?.Invoke(this, EventArgs.Empty); + } + + protected virtual void OnSelectedDataBindingChanged() + { + SelectedDataBindingChanged?.Invoke(this, EventArgs.Empty); + } + private void CoreServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e) { if (!_doTick) return; @@ -89,7 +137,9 @@ namespace Artemis.UI.Shared.Services return; if (renderElement.Suspended) + { renderElement.Disable(); + } else { renderElement.Enable(); @@ -104,7 +154,6 @@ namespace Artemis.UI.Shared.Services } public ReadOnlyCollection RegisteredPropertyEditors => _registeredPropertyEditors.AsReadOnly(); - public bool Playing { get; set; } public bool SuspendEditing @@ -183,10 +232,10 @@ namespace Artemis.UI.Shared.Services // No need to deactivate the profile, if needed it will be deactivated next update if (SelectedProfileConfiguration != null) SelectedProfileConfiguration.IsBeingEdited = false; - + PreviousSelectedProfileConfiguration = SelectedProfileConfiguration; SelectedProfileConfiguration = profileConfiguration; - + // The new profile may need activation if (SelectedProfileConfiguration != null) { @@ -319,10 +368,8 @@ namespace Artemis.UI.Shared.Services if (existing != null) { if (existing.Plugin != plugin) - { throw new ArtemisSharedUIException($"Cannot register property editor for type {supportedType.Name} because an editor was already " + $"registered by {existing.Plugin}"); - } return existing; } @@ -367,10 +414,8 @@ namespace Artemis.UI.Shared.Services if (snapToCurrentTime) // Snap to the current time - { if (Math.Abs(time.TotalMilliseconds - CurrentTime.TotalMilliseconds) < tolerance.TotalMilliseconds) return CurrentTime; - } if (snapTimes != null) { @@ -406,9 +451,13 @@ namespace Artemis.UI.Shared.Services viewModelType = registration.ViewModelType.MakeGenericType(layerProperty.GetType().GenericTypeArguments); } else if (registration != null) + { viewModelType = registration.ViewModelType; + } else + { return null; + } if (viewModelType == null) return null; @@ -428,6 +477,16 @@ namespace Artemis.UI.Shared.Services .ToList(); } + public event EventHandler? SelectedProfileChanged; + public event EventHandler? SelectedProfileSaved; + public event EventHandler? SelectedProfileElementChanged; + public event EventHandler? SelectedProfileElementSaved; + public event EventHandler? SelectedDataBindingChanged; + public event EventHandler? CurrentTimeChanged; + public event EventHandler? PixelsPerSecondChanged; + public event EventHandler? SuspendEditingChanged; + public event EventHandler? ProfilePreviewUpdated; + #region Copy/paste public ProfileElement? DuplicateProfileElement(ProfileElement profileElement) @@ -507,64 +566,5 @@ namespace Artemis.UI.Shared.Services } #endregion - - #region Events - - public event EventHandler? SelectedProfileChanged; - public event EventHandler? SelectedProfileSaved; - public event EventHandler? SelectedProfileElementChanged; - public event EventHandler? SelectedProfileElementSaved; - public event EventHandler? SelectedDataBindingChanged; - public event EventHandler? CurrentTimeChanged; - public event EventHandler? PixelsPerSecondChanged; - public event EventHandler? SuspendEditingChanged; - public event EventHandler? ProfilePreviewUpdated; - - protected virtual void OnSelectedProfileChanged(ProfileConfigurationEventArgs e) - { - SelectedProfileChanged?.Invoke(this, e); - } - - protected virtual void OnSelectedProfileUpdated(ProfileConfigurationEventArgs e) - { - SelectedProfileSaved?.Invoke(this, e); - } - - protected virtual void OnSelectedProfileElementChanged(RenderProfileElementEventArgs e) - { - SelectedProfileElementChanged?.Invoke(this, e); - } - - protected virtual void OnSelectedProfileElementUpdated(RenderProfileElementEventArgs e) - { - SelectedProfileElementSaved?.Invoke(this, e); - } - - protected virtual void OnCurrentTimeChanged() - { - CurrentTimeChanged?.Invoke(this, EventArgs.Empty); - } - - protected virtual void OnPixelsPerSecondChanged() - { - PixelsPerSecondChanged?.Invoke(this, EventArgs.Empty); - } - - protected virtual void OnSuspendEditingChanged() - { - SuspendEditingChanged?.Invoke(this, EventArgs.Empty); - } - - protected virtual void OnProfilePreviewUpdated() - { - ProfilePreviewUpdated?.Invoke(this, EventArgs.Empty); - } - - protected virtual void OnSelectedDataBindingChanged() - { - SelectedDataBindingChanged?.Invoke(this, EventArgs.Empty); - } - - #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Utilities/TypeUtilities.cs b/src/Artemis.UI.Shared/Utilities/TypeUtilities.cs new file mode 100644 index 000000000..b979006e4 --- /dev/null +++ b/src/Artemis.UI.Shared/Utilities/TypeUtilities.cs @@ -0,0 +1,43 @@ +using System; +using System.Security.Cryptography; +using System.Text; +using System.Windows.Media; +using Artemis.Core; +using Artemis.Core.Services; +using SkiaSharp; +using SkiaSharp.Views.WPF; + +namespace Artemis.UI.Shared +{ + /// + /// Provides UI-oriented utilities for types + /// + public static class TypeUtilities + { + internal static INodeService? NodeService; + + /// + /// Creates a tuple containing a color and a slightly darkened color for a given type + /// + /// The type to create the color for + public static (Color, Color) GetTypeColors(Type type) + { + if (type == typeof(object)) + return (new SKColor(0xFFFFFF).ToColor(), new SKColor(0xFFFFFF).Darken(0.35f).ToColor()); + + TypeColorRegistration? typeColorRegistration = NodeService?.GetTypeColor(type); + if (typeColorRegistration != null) + return (typeColorRegistration.Color.ToColor(), typeColorRegistration.DarkenedColor.ToColor()); + + // Come up with a random color based on the type name that should be the same each time + MD5 md5Hasher = MD5.Create(); + byte[] hashed = md5Hasher.ComputeHash(Encoding.UTF8.GetBytes(type.FullName!)); + int hash = BitConverter.ToInt32(hashed, 0); + + SKColor baseColor = SKColor.FromHsl(hash % 255, 50 + hash % 50, 50); + SKColor darkenedColor = baseColor.Darken(0.35f); + + return (baseColor.ToColor(), darkenedColor.ToColor()); + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs index a587da7cc..08f3659e9 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs @@ -94,7 +94,8 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions private void Update(RenderProfileElement renderProfileElement) { - if (RenderProfileElement != null) RenderProfileElement.Timeline.PropertyChanged -= TimelineOnPropertyChanged; + if (RenderProfileElement != null) + RenderProfileElement.Timeline.PropertyChanged -= TimelineOnPropertyChanged; RenderProfileElement = renderProfileElement; @@ -102,7 +103,8 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions NotifyOfPropertyChange(nameof(AlwaysFinishTimeline)); NotifyOfPropertyChange(nameof(ConditionBehaviourEnabled)); - RenderProfileElement.Timeline.PropertyChanged += TimelineOnPropertyChanged; + if (RenderProfileElement != null) + RenderProfileElement.Timeline.PropertyChanged += TimelineOnPropertyChanged; } #region Event handlers diff --git a/src/Artemis.UI/Services/RegistrationService.cs b/src/Artemis.UI/Services/RegistrationService.cs index ec5633647..5adbd456b 100644 --- a/src/Artemis.UI/Services/RegistrationService.cs +++ b/src/Artemis.UI/Services/RegistrationService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Linq; using Artemis.Core; using Artemis.Core.Services; @@ -12,6 +13,7 @@ using Artemis.UI.Shared.Services; using Artemis.UI.SkiaSharp; using Artemis.VisualScripting.Nodes; using Serilog; +using SkiaSharp; namespace Artemis.UI.Services { @@ -119,6 +121,13 @@ namespace Artemis.UI.Services public void RegisterBuiltInNodeTypes() { + _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(IList), new SKColor(0xFFC842FF)); + 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.VisualScripting/Editor/Controls/VisualScriptCablePresenter.cs b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptCablePresenter.cs index 85f6a8a7c..22bfdcf07 100644 --- a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptCablePresenter.cs +++ b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptCablePresenter.cs @@ -7,6 +7,7 @@ using System.Windows.Input; using System.Windows.Media; using System.Windows.Shapes; using Artemis.Core; +using Artemis.UI.Shared; using Artemis.VisualScripting.Editor.Controls.Wrapper; namespace Artemis.VisualScripting.Editor.Controls @@ -51,7 +52,7 @@ namespace Artemis.VisualScripting.Editor.Controls public Point ValuePosition { - get => (Point)GetValue(ValuePositionProperty); + get => (Point) GetValue(ValuePositionProperty); set => SetValue(ValuePositionProperty, value); } @@ -63,7 +64,7 @@ namespace Artemis.VisualScripting.Editor.Controls { _path = GetTemplateChild(PART_PATH) as Path ?? throw new NullReferenceException($"The Path '{PART_PATH}' is missing."); _path.MouseDown += OnPathMouseDown; - + Unloaded += OnUnloaded; } @@ -110,7 +111,7 @@ namespace Artemis.VisualScripting.Editor.Controls UpdateBorderBrush(); UpdateValuePosition(); } - + private void OnPinPropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(VisualScriptPin.AbsolutePosition)) @@ -119,15 +120,15 @@ namespace Artemis.VisualScripting.Editor.Controls private void UpdateBorderBrush() { - // if (Cable.From.Pin.Type.IsAssignableTo(typeof(IList))) - // BorderBrush = new SolidColorBrush(Colors.MediumPurple); + (Color color, Color _) = TypeUtilities.GetTypeColors(Cable.From.Pin.Type); + BorderBrush = new SolidColorBrush(color); } private void UpdateValuePosition() { if (Cable.From == null || Cable.To == null) return; - + ValuePosition = new Point( Cable.From.AbsolutePosition.X + ((Cable.To.AbsolutePosition.X - Cable.From.AbsolutePosition.X) / 2), Cable.From.AbsolutePosition.Y + ((Cable.To.AbsolutePosition.Y - Cable.From.AbsolutePosition.Y) / 2) diff --git a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPinPresenter.cs b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPinPresenter.cs index 76c15b45c..88154da03 100644 --- a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPinPresenter.cs +++ b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPinPresenter.cs @@ -5,6 +5,7 @@ using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using Artemis.Core; +using Artemis.UI.Shared; using Artemis.VisualScripting.Editor.Controls.Wrapper; namespace Artemis.VisualScripting.Editor.Controls @@ -108,9 +109,17 @@ namespace Artemis.VisualScripting.Editor.Controls if (args.NewValue is VisualScriptPin newPin) newPin.Node.Node.PropertyChanged += OnNodePropertyChanged; + UpdateBrushes(); UpdateAbsoluteLocation(); } + private void UpdateBrushes() + { + (Color border, Color background) = TypeUtilities.GetTypeColors(Pin.Pin.Type); + Background = new SolidColorBrush(background); + BorderBrush = new SolidColorBrush(border); + } + private void UpdateAbsoluteLocation() { if ((Pin == null) || (_nodePresenter == null)) return; diff --git a/src/Artemis.VisualScripting/Editor/Styles/VisualScriptCablePresenter.xaml b/src/Artemis.VisualScripting/Editor/Styles/VisualScriptCablePresenter.xaml index 0a507021d..223a2b870 100644 --- a/src/Artemis.VisualScripting/Editor/Styles/VisualScriptCablePresenter.xaml +++ b/src/Artemis.VisualScripting/Editor/Styles/VisualScriptCablePresenter.xaml @@ -62,26 +62,6 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/src/Artemis.VisualScripting/Editor/Styles/VisualScriptPinPresenter.xaml b/src/Artemis.VisualScripting/Editor/Styles/VisualScriptPinPresenter.xaml index 2a6deed99..f485928e1 100644 --- a/src/Artemis.VisualScripting/Editor/Styles/VisualScriptPinPresenter.xaml +++ b/src/Artemis.VisualScripting/Editor/Styles/VisualScriptPinPresenter.xaml @@ -80,31 +80,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - From 05d82229910311be1b7283cb7aed1f2f3a6c773d Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 19 Aug 2021 16:13:37 +0200 Subject: [PATCH 13/47] Nodes - Ensure models using scripts always have a script --- .../Profile/DataBindings/DataBinding.cs | 19 ++++--- .../Models/Profile/RenderProfileElement.cs | 21 +++++--- .../ProfileConfiguration.cs | 15 ++++-- .../VisualScripting/Interfaces/INodeScript.cs | 1 + .../VisualScripting/NodeScript.cs | 53 +++++++------------ .../DisplayConditionsView.xaml | 18 +++++-- .../StaticIntegerValueNodeCustomViewModel.cs | 7 ++- 7 files changed, 78 insertions(+), 56 deletions(-) diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs index 62fcaea73..c95480bae 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs @@ -17,9 +17,10 @@ namespace Artemis.Core { LayerProperty = dataBindingRegistration.LayerProperty; Entity = new DataBindingEntity(); - Script = new NodeScript(LayerProperty.PropertyDescription.Name ?? LayerProperty.Path, ""); ApplyRegistration(dataBindingRegistration); + Script = new NodeScript(GetScriptName(), "The value to put into the data binding"); + Save(); } @@ -27,7 +28,7 @@ namespace Artemis.Core { LayerProperty = layerProperty; Entity = entity; - Script = new NodeScript(LayerProperty.PropertyDescription.Name ?? LayerProperty.Path, ""); + Script = new NodeScript(GetScriptName(), "The value to put into the data binding"); // Load will add children so be initialized before that Load(); @@ -116,8 +117,7 @@ namespace Artemis.Core if (Registration != null) Registration.DataBinding = null; - Script?.Dispose(); - Script = null; + Script.Dispose(); } } @@ -213,6 +213,13 @@ namespace Artemis.Core _reapplyValue = true; } + private string GetScriptName() + { + if (LayerProperty.GetAllDataBindingRegistrations().Count == 1) + return LayerProperty.PropertyDescription.Name ?? LayerProperty.Path; + return $"{LayerProperty.PropertyDescription.Name ?? LayerProperty.Path} - {Registration?.DisplayName}"; + } + /// public void Dispose() { @@ -238,8 +245,8 @@ namespace Artemis.Core Script.Dispose(); Script = Entity.NodeScript != null - ? new NodeScript(Entity.NodeScript) - : new NodeScript(LayerProperty.PropertyDescription.Name ?? LayerProperty.Path, ""); + ? new NodeScript(GetScriptName(), "The value to put into the data binding", Entity.NodeScript) + : new NodeScript(GetScriptName(), "The value to put into the data binding"); } /// diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs index 8d69a0980..7e841350b 100644 --- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs @@ -18,13 +18,17 @@ namespace Artemis.Core { private SKRectI _bounds; private SKPath? _path; + private readonly string _typeDisplayName; internal RenderProfileElement(Profile profile) : base(profile) { + _typeDisplayName = this is Layer ? "layer" : "folder"; + _displayCondition = new NodeScript($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active"); + Timeline = new Timeline(); ExpandedPropertyGroups = new List(); LayerEffectsList = new List(); - + LayerEffectStore.LayerEffectAdded += LayerEffectStoreOnLayerEffectAdded; LayerEffectStore.LayerEffectRemoved += LayerEffectStoreOnLayerEffectRemoved; } @@ -59,15 +63,17 @@ namespace Artemis.Core foreach (BaseLayerEffect baseLayerEffect in LayerEffects) baseLayerEffect.Dispose(); - DisplayCondition?.Dispose(); + DisplayCondition.Dispose(); base.Dispose(disposing); } internal void LoadRenderElement() { - DisplayCondition = RenderElementEntity.NodeScript != null ? new NodeScript(RenderElementEntity.NodeScript) : null; - + DisplayCondition = RenderElementEntity.NodeScript != null + ? new NodeScript($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active", RenderElementEntity.NodeScript) + : new NodeScript($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active"); + Timeline = RenderElementEntity.Timeline != null ? new Timeline(RenderElementEntity.Timeline) : new Timeline(); @@ -354,14 +360,15 @@ namespace Artemis.Core protected set => SetAndNotify(ref _displayConditionMet, value); } - private NodeScript? _displayCondition; + private NodeScript _displayCondition; private bool _displayConditionMet; private bool _toggledOnByEvent = false; + /// /// Gets or sets the root display condition group /// - public NodeScript? DisplayCondition + public NodeScript DisplayCondition { get => _displayCondition; set => SetAndNotify(ref _displayCondition, value); @@ -378,7 +385,7 @@ namespace Artemis.Core return; } - if (DisplayCondition == null) + if (!DisplayCondition.HasNodes) { DisplayConditionMet = true; return; diff --git a/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs b/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs index 162567ab7..2f29cdd2a 100644 --- a/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs +++ b/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs @@ -28,6 +28,7 @@ namespace Artemis.Core Entity = new ProfileConfigurationEntity(); Icon = new ProfileConfigurationIcon(Entity) {MaterialIcon = icon}; + ActivationCondition = new NodeScript("Activate profile", "Whether or not the profile should be active"); } internal ProfileConfiguration(ProfileCategory category, ProfileConfigurationEntity entity) @@ -38,6 +39,8 @@ namespace Artemis.Core Entity = entity; Icon = new ProfileConfigurationIcon(Entity); + ActivationCondition = new NodeScript("Activate profile", "Whether or not the profile should be active"); + Load(); } @@ -130,7 +133,7 @@ namespace Artemis.Core /// Gets the data model condition that must evaluate to for this profile to be activated /// alongside any activation requirements of the , if set /// - public NodeScript? ActivationCondition { get; set; } + public NodeScript ActivationCondition { get; set; } /// /// Gets or sets the module this profile uses @@ -168,7 +171,7 @@ namespace Artemis.Core if (_disposed) throw new ObjectDisposedException("ProfileConfiguration"); - if (ActivationCondition == null) + if (!ActivationCondition.HasNodes) ActivationConditionMet = true; else { @@ -216,7 +219,7 @@ namespace Artemis.Core public void Dispose() { _disposed = true; - ActivationCondition?.Dispose(); + ActivationCondition.Dispose(); } #endregion @@ -237,8 +240,10 @@ namespace Artemis.Core Icon.Load(); - ActivationCondition?.Dispose(); - ActivationCondition = Entity.ActivationCondition != null ? new NodeScript(Entity.ActivationCondition) : null; + ActivationCondition.Dispose(); + ActivationCondition = Entity.ActivationCondition != null + ? new NodeScript("Activate profile", "Whether or not the profile should be active", Entity.ActivationCondition) + : new NodeScript("Activate profile", "Whether or not the profile should be active"); EnableHotkey = Entity.EnableHotkey != null ? new ProfileConfigurationHotkey(Entity.EnableHotkey) : null; DisableHotkey = Entity.DisableHotkey != null ? new ProfileConfigurationHotkey(Entity.DisableHotkey) : null; diff --git a/src/Artemis.Core/VisualScripting/Interfaces/INodeScript.cs b/src/Artemis.Core/VisualScripting/Interfaces/INodeScript.cs index abfa87b75..c05ba214d 100644 --- a/src/Artemis.Core/VisualScripting/Interfaces/INodeScript.cs +++ b/src/Artemis.Core/VisualScripting/Interfaces/INodeScript.cs @@ -8,6 +8,7 @@ namespace Artemis.Core { string Name { get; } string Description { get; } + bool HasNodes { get; } IEnumerable Nodes { get; } diff --git a/src/Artemis.Core/VisualScripting/NodeScript.cs b/src/Artemis.Core/VisualScripting/NodeScript.cs index 8d30b5fbd..ecb992895 100644 --- a/src/Artemis.Core/VisualScripting/NodeScript.cs +++ b/src/Artemis.Core/VisualScripting/NodeScript.cs @@ -16,13 +16,13 @@ namespace Artemis.Core public string Name { get; } public string Description { get; } + public bool HasNodes => _nodes.Count > 1; private readonly List _nodes = new(); public IEnumerable Nodes => new ReadOnlyCollection(_nodes); protected INode ExitNode { get; set; } public abstract Type ResultType { get; } - public abstract void CreateExitNode(string name, string description = ""); #endregion @@ -38,14 +38,12 @@ namespace Artemis.Core NodeTypeStore.NodeTypeRemoved += NodeTypeStoreOnNodeTypeChanged; } - internal NodeScript(NodeScriptEntity entity) + internal NodeScript(string name, string description, NodeScriptEntity entity) { - this.Name = entity.Name; - this.Description = entity.Description; + this.Name = name; + this.Description = description; this.Entity = entity; - Load(); - NodeTypeStore.NodeTypeAdded += NodeTypeStoreOnNodeTypeChanged; NodeTypeStore.NodeTypeRemoved += NodeTypeStoreOnNodeTypeChanged; } @@ -85,40 +83,25 @@ namespace Artemis.Core /// public void Load() { - bool gotExitNode = false; - // Create nodes Dictionary nodes = new(); foreach (NodeEntity entityNode in Entity.Nodes) { - INode? node = LoadNode(entityNode); + INode? node = LoadNode(entityNode, entityNode.IsExitNode ? ExitNode : null); if (node == null) continue; - - if (node.IsExitNode) - gotExitNode = true; - nodes.Add(entityNode.Id, node); } - - if (!gotExitNode) - CreateExitNode("Exit node"); - + LoadConnections(nodes); _nodes.Clear(); _nodes.AddRange(nodes.Values); } - private INode? LoadNode(NodeEntity nodeEntity) + private INode? LoadNode(NodeEntity nodeEntity, INode? node) { - INode node; - if (nodeEntity.IsExitNode) - { - CreateExitNode(nodeEntity.Name, nodeEntity.Description); - node = ExitNode; - } - else + if (node == null) { NodeTypeRegistration? nodeTypeRegistration = NodeTypeStore.Get(nodeEntity.PluginId, nodeEntity.Type); if (nodeTypeRegistration == null) @@ -127,7 +110,12 @@ namespace Artemis.Core // Create the node node = nodeTypeRegistration.NodeData.CreateNode(nodeEntity); } - + else + { + node.X = nodeEntity.X; + node.Y = nodeEntity.Y; + } + // Restore pin collections foreach (NodePinCollectionEntity entityNodePinCollection in nodeEntity.PinCollections) { @@ -280,18 +268,17 @@ namespace Artemis.Core public override Type ResultType => typeof(T); - public override void CreateExitNode(string name, string description = "") - { - ExitNode = new ExitNode(name, description); - } - #endregion #region Constructors - internal NodeScript(NodeScriptEntity entity) - : base(entity) + internal NodeScript(string name, string description, NodeScriptEntity entity) + : base(name, description, entity) { + ExitNode = new ExitNode(name, description); + AddNode(ExitNode); + + Load(); } public NodeScript(string name, string description) diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml index a9f8b3867..16a6b7000 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml @@ -45,7 +45,10 @@ - + + + + - Click to edit script + + + + + + + Click to edit script + + + diff --git a/src/Artemis.VisualScripting/Nodes/CustomViewModels/StaticIntegerValueNodeCustomViewModel.cs b/src/Artemis.VisualScripting/Nodes/CustomViewModels/StaticIntegerValueNodeCustomViewModel.cs index dc18ea51d..14e63812c 100644 --- a/src/Artemis.VisualScripting/Nodes/CustomViewModels/StaticIntegerValueNodeCustomViewModel.cs +++ b/src/Artemis.VisualScripting/Nodes/CustomViewModels/StaticIntegerValueNodeCustomViewModel.cs @@ -13,7 +13,12 @@ namespace Artemis.VisualScripting.Nodes.CustomViewModels public int Input { - get => (int) _node.Storage; + get + { + if (_node.Storage is long longInput) + return (int) longInput; + return (int) _node.Storage; + } set { _node.Storage = value; From c6a73f01f01c40acb15a0bdc4bdf0cdfe28a57ed Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 19 Aug 2021 23:58:32 +0200 Subject: [PATCH 14/47] Node editor - Tweak grid size and apply grid size to script Type coloring - Fix object's white color --- src/Artemis.UI.Shared/Utilities/TypeUtilities.cs | 2 +- .../Editor/Controls/VisualScriptPresenter.cs | 2 +- .../Editor/Styles/VisualScriptPresenter.xaml | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Artemis.UI.Shared/Utilities/TypeUtilities.cs b/src/Artemis.UI.Shared/Utilities/TypeUtilities.cs index b979006e4..314cdb989 100644 --- a/src/Artemis.UI.Shared/Utilities/TypeUtilities.cs +++ b/src/Artemis.UI.Shared/Utilities/TypeUtilities.cs @@ -23,7 +23,7 @@ namespace Artemis.UI.Shared public static (Color, Color) GetTypeColors(Type type) { if (type == typeof(object)) - return (new SKColor(0xFFFFFF).ToColor(), new SKColor(0xFFFFFF).Darken(0.35f).ToColor()); + return (SKColors.White.ToColor(), SKColors.White.Darken(0.35f).ToColor()); TypeColorRegistration? typeColorRegistration = NodeService?.GetTypeColor(type); if (typeColorRegistration != null) diff --git a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPresenter.cs b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPresenter.cs index bd47ec9ee..d7c4016c9 100644 --- a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPresenter.cs +++ b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPresenter.cs @@ -121,7 +121,7 @@ namespace Artemis.VisualScripting.Editor.Controls } public static readonly DependencyProperty GridSizeProperty = DependencyProperty.Register( - "GridSize", typeof(int), typeof(VisualScriptPresenter), new PropertyMetadata(24)); + "GridSize", typeof(int), typeof(VisualScriptPresenter), new PropertyMetadata(12)); public int GridSize { diff --git a/src/Artemis.VisualScripting/Editor/Styles/VisualScriptPresenter.xaml b/src/Artemis.VisualScripting/Editor/Styles/VisualScriptPresenter.xaml index afa908aa7..eb9341488 100644 --- a/src/Artemis.VisualScripting/Editor/Styles/VisualScriptPresenter.xaml +++ b/src/Artemis.VisualScripting/Editor/Styles/VisualScriptPresenter.xaml @@ -87,13 +87,13 @@ - + - - - - + + + + From 3ed3cd1b1ee17794f2b9c08c736fba2a8c35e95b Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 20 Aug 2021 00:14:11 +0200 Subject: [PATCH 15/47] UI - Fix double registration of default UI types --- src/Artemis.UI/Screens/RootViewModel.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Artemis.UI/Screens/RootViewModel.cs b/src/Artemis.UI/Screens/RootViewModel.cs index b5edea137..fc35027f8 100644 --- a/src/Artemis.UI/Screens/RootViewModel.cs +++ b/src/Artemis.UI/Screens/RootViewModel.cs @@ -32,11 +32,12 @@ namespace Artemis.UI.Screens private readonly IWindowManager _windowManager; private readonly PluginSetting _windowSize; + private static bool _registeredBuiltInTypes; private bool _lostFocus; private ISnackbarMessageQueue _mainMessageQueue; private MaterialWindow _window; private string _windowTitle; - + public RootViewModel( IKernel kernel, IEventAggregator eventAggregator, @@ -169,10 +170,14 @@ namespace Artemis.UI.Screens SidebarViewModel.SelectedScreenChanged += SidebarViewModelOnSelectedScreenChanged; ActiveItem = SidebarViewModel.SelectedScreen; - _builtInRegistrationService.RegisterBuiltInDataModelDisplays(); - _builtInRegistrationService.RegisterBuiltInDataModelInputs(); - _builtInRegistrationService.RegisterBuiltInPropertyEditors(); - _builtInRegistrationService.RegisterBuiltInNodeTypes(); + if (!_registeredBuiltInTypes) + { + _builtInRegistrationService.RegisterBuiltInDataModelDisplays(); + _builtInRegistrationService.RegisterBuiltInDataModelInputs(); + _builtInRegistrationService.RegisterBuiltInPropertyEditors(); + _builtInRegistrationService.RegisterBuiltInNodeTypes(); + _registeredBuiltInTypes = true; + } _window = (MaterialWindow) View; From 836e979991e9b56a64f4e35d5f620a701023aa30 Mon Sep 17 00:00:00 2001 From: Robert Date: Sat, 21 Aug 2021 12:15:01 +0200 Subject: [PATCH 16/47] Nodes - Provide scripts with a context Nodes - Inform nodes about the script they're being initialized for Nodes - Added float nodes matching the existing other number types Core - Add API for retrieving data binding values via the interface --- .../Profile/DataBindings/DataBinding.cs | 8 +- .../DataBindings/DataBindingRegistration.cs | 9 ++ .../DataBindings/IDataBindingRegistration.cs | 15 +++- .../Models/Profile/RenderProfileElement.cs | 6 +- .../ProfileConfiguration.cs | 8 +- src/Artemis.Core/Services/NodeService.cs | 6 +- .../VisualScripting/CustomNodeViewModel.cs | 8 +- .../VisualScripting/Interfaces/INode.cs | 2 +- .../VisualScripting/Interfaces/INodeScript.cs | 2 + src/Artemis.Core/VisualScripting/Node.cs | 9 +- src/Artemis.Core/VisualScripting/NodeData.cs | 6 +- .../VisualScripting/NodeScript.cs | 18 ++-- .../DisplayConditionsViewModel.cs | 2 - .../Artemis.VisualScripting.csproj | 3 + .../Editor/Controls/VisualScriptPresenter.cs | 3 +- .../CustomViewModels/CustomNodeViewModel.cs | 34 +++++++ .../DataModelNodeCustomViewModel.cs | 32 +++++++ .../StaticDoubleValueNodeCustomViewModel.cs | 24 ----- .../StaticIntegerValueNodeCustomViewModel.cs | 29 ------ .../StaticStringValueNodeCustomViewModel.cs | 24 ----- .../StaticValueNodeViewModels.cs | 89 +++++++++++++++++++ .../CustomViews/DataModelNodeCustomView.xaml | 2 +- .../StaticFloatValueNodeCustomView.xaml | 12 +++ .../Nodes/DataModelNode.cs | 9 +- .../Nodes/StaticValueNodes.cs | 35 +++++++- src/Artemis.VisualScripting/Nodes/SumNode.cs | 32 +++++++ 26 files changed, 305 insertions(+), 122 deletions(-) create mode 100644 src/Artemis.VisualScripting/Nodes/CustomViewModels/CustomNodeViewModel.cs delete mode 100644 src/Artemis.VisualScripting/Nodes/CustomViewModels/StaticDoubleValueNodeCustomViewModel.cs delete mode 100644 src/Artemis.VisualScripting/Nodes/CustomViewModels/StaticIntegerValueNodeCustomViewModel.cs delete mode 100644 src/Artemis.VisualScripting/Nodes/CustomViewModels/StaticStringValueNodeCustomViewModel.cs create mode 100644 src/Artemis.VisualScripting/Nodes/CustomViewModels/StaticValueNodeViewModels.cs create mode 100644 src/Artemis.VisualScripting/Nodes/CustomViews/StaticFloatValueNodeCustomView.xaml diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs index c95480bae..0cf5fc9fb 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs @@ -19,7 +19,7 @@ namespace Artemis.Core Entity = new DataBindingEntity(); ApplyRegistration(dataBindingRegistration); - Script = new NodeScript(GetScriptName(), "The value to put into the data binding"); + Script = new NodeScript(GetScriptName(), "The value to put into the data binding", LayerProperty.ProfileElement.Profile); Save(); } @@ -28,7 +28,7 @@ namespace Artemis.Core { LayerProperty = layerProperty; Entity = entity; - Script = new NodeScript(GetScriptName(), "The value to put into the data binding"); + Script = new NodeScript(GetScriptName(), "The value to put into the data binding", LayerProperty.ProfileElement.Profile); // Load will add children so be initialized before that Load(); @@ -245,8 +245,8 @@ namespace Artemis.Core Script.Dispose(); Script = Entity.NodeScript != null - ? new NodeScript(GetScriptName(), "The value to put into the data binding", Entity.NodeScript) - : new NodeScript(GetScriptName(), "The value to put into the data binding"); + ? new NodeScript(GetScriptName(), "The value to put into the data binding", Entity.NodeScript, LayerProperty.ProfileElement.Profile) + : new NodeScript(GetScriptName(), "The value to put into the data binding", LayerProperty.ProfileElement.Profile); } /// diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs index ab50c44c9..cab335a43 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs @@ -40,6 +40,9 @@ namespace Artemis.Core /// public string DisplayName { get; } + /// + public Type ValueType => typeof(TProperty); + /// /// Gets the data binding created using this registration /// @@ -74,5 +77,11 @@ namespace Artemis.Core // The related entity is left behind, just in case the data binding is added back later LayerProperty.DisableDataBinding(DataBinding); } + + /// + public object? GetValue() + { + return Getter(); + } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingRegistration.cs b/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingRegistration.cs index c1321d4c3..4b9ceeb94 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingRegistration.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingRegistration.cs @@ -1,4 +1,6 @@ -namespace Artemis.Core +using System; + +namespace Artemis.Core { /// /// Represents a data binding registration @@ -10,6 +12,11 @@ /// string DisplayName { get; } + /// + /// Gets the type of the value this data binding registration points to + /// + Type ValueType { get; } + /// /// Returns the data binding applied using this registration /// @@ -25,5 +32,11 @@ /// If present, removes the current data binding /// void ClearDataBinding(); + + /// + /// Gets the value of the data binding + /// + /// A value matching the type of + object? GetValue(); } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs index 7e841350b..7003dd985 100644 --- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs @@ -23,7 +23,7 @@ namespace Artemis.Core internal RenderProfileElement(Profile profile) : base(profile) { _typeDisplayName = this is Layer ? "layer" : "folder"; - _displayCondition = new NodeScript($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active"); + _displayCondition = new NodeScript($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active", Profile); Timeline = new Timeline(); ExpandedPropertyGroups = new List(); @@ -71,8 +71,8 @@ namespace Artemis.Core internal void LoadRenderElement() { DisplayCondition = RenderElementEntity.NodeScript != null - ? new NodeScript($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active", RenderElementEntity.NodeScript) - : new NodeScript($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active"); + ? new NodeScript($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active", RenderElementEntity.NodeScript, Profile) + : new NodeScript($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active", Profile); Timeline = RenderElementEntity.Timeline != null ? new Timeline(RenderElementEntity.Timeline) diff --git a/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs b/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs index 2f29cdd2a..b2ce08edb 100644 --- a/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs +++ b/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs @@ -28,7 +28,7 @@ namespace Artemis.Core Entity = new ProfileConfigurationEntity(); Icon = new ProfileConfigurationIcon(Entity) {MaterialIcon = icon}; - ActivationCondition = new NodeScript("Activate profile", "Whether or not the profile should be active"); + ActivationCondition = new NodeScript("Activate profile", "Whether or not the profile should be active", this); } internal ProfileConfiguration(ProfileCategory category, ProfileConfigurationEntity entity) @@ -39,7 +39,7 @@ namespace Artemis.Core Entity = entity; Icon = new ProfileConfigurationIcon(Entity); - ActivationCondition = new NodeScript("Activate profile", "Whether or not the profile should be active"); + ActivationCondition = new NodeScript("Activate profile", "Whether or not the profile should be active", this); Load(); } @@ -242,8 +242,8 @@ namespace Artemis.Core ActivationCondition.Dispose(); ActivationCondition = Entity.ActivationCondition != null - ? new NodeScript("Activate profile", "Whether or not the profile should be active", Entity.ActivationCondition) - : new NodeScript("Activate profile", "Whether or not the profile should be active"); + ? new NodeScript("Activate profile", "Whether or not the profile should be active", Entity.ActivationCondition, this) + : new NodeScript("Activate profile", "Whether or not the profile should be active", this); EnableHotkey = Entity.EnableHotkey != null ? new ProfileConfigurationHotkey(Entity.EnableHotkey) : null; DisableHotkey = Entity.DisableHotkey != null ? new ProfileConfigurationHotkey(Entity.DisableHotkey) : null; diff --git a/src/Artemis.Core/Services/NodeService.cs b/src/Artemis.Core/Services/NodeService.cs index 66ffd7804..fd09d8de5 100644 --- a/src/Artemis.Core/Services/NodeService.cs +++ b/src/Artemis.Core/Services/NodeService.cs @@ -53,7 +53,7 @@ namespace Artemis.Core.Services string description = nodeAttribute?.Description ?? string.Empty; string category = nodeAttribute?.Category ?? string.Empty; - NodeData nodeData = new(plugin, nodeType, name, description, category, e => CreateNode(e, nodeType)); + NodeData nodeData = new(plugin, nodeType, name, description, category, (s, e) => CreateNode(s, e, nodeType)); return NodeTypeStore.Add(nodeData); } @@ -65,7 +65,7 @@ namespace Artemis.Core.Services return NodeTypeStore.AddColor(type, color, plugin); } - private INode CreateNode(NodeEntity? entity, Type nodeType) + private INode CreateNode(INodeScript script, NodeEntity? entity, Type nodeType) { INode node = _kernel.Get(nodeType) as INode ?? throw new InvalidOperationException($"Node {nodeType} is not an INode"); @@ -79,7 +79,7 @@ namespace Artemis.Core.Services if (node is CustomViewModelNode customViewModelNode) customViewModelNode.BaseCustomViewModel = _kernel.Get(customViewModelNode.CustomViewModelType, new ConstructorArgument("node", node)); - node.Initialize(); + node.Initialize(script); return node; } diff --git a/src/Artemis.Core/VisualScripting/CustomNodeViewModel.cs b/src/Artemis.Core/VisualScripting/CustomNodeViewModel.cs index c53cf951e..5975ed806 100644 --- a/src/Artemis.Core/VisualScripting/CustomNodeViewModel.cs +++ b/src/Artemis.Core/VisualScripting/CustomNodeViewModel.cs @@ -2,14 +2,8 @@ namespace Artemis.Core { - public class CustomNodeViewModel : CorePropertyChanged + public interface ICustomNodeViewModel { - [JsonIgnore] public INode Node { get; } - - public CustomNodeViewModel(INode node) - { - Node = node; - } } } \ No newline at end of file diff --git a/src/Artemis.Core/VisualScripting/Interfaces/INode.cs b/src/Artemis.Core/VisualScripting/Interfaces/INode.cs index 5a548a4b9..f17fd3352 100644 --- a/src/Artemis.Core/VisualScripting/Interfaces/INode.cs +++ b/src/Artemis.Core/VisualScripting/Interfaces/INode.cs @@ -19,7 +19,7 @@ namespace Artemis.Core event EventHandler Resetting; - void Initialize(); + void Initialize(INodeScript script); void Evaluate(); void Reset(); } diff --git a/src/Artemis.Core/VisualScripting/Interfaces/INodeScript.cs b/src/Artemis.Core/VisualScripting/Interfaces/INodeScript.cs index c05ba214d..76a6df648 100644 --- a/src/Artemis.Core/VisualScripting/Interfaces/INodeScript.cs +++ b/src/Artemis.Core/VisualScripting/Interfaces/INodeScript.cs @@ -14,6 +14,8 @@ namespace Artemis.Core Type ResultType { get; } + object? Context { get; set; } + void Run(); void AddNode(INode node); void RemoveNode(INode node); diff --git a/src/Artemis.Core/VisualScripting/Node.cs b/src/Artemis.Core/VisualScripting/Node.cs index 90a5f404e..5b504a8ec 100644 --- a/src/Artemis.Core/VisualScripting/Node.cs +++ b/src/Artemis.Core/VisualScripting/Node.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Linq; namespace Artemis.Core { @@ -107,7 +108,7 @@ namespace Artemis.Core OnPropertyChanged(nameof(Pins)); return pin; } - + protected bool RemovePin(Pin pin) { bool isRemoved = _pins.Remove(pin); @@ -136,7 +137,9 @@ namespace Artemis.Core return pin; } - public virtual void Initialize() { } + public virtual void Initialize(INodeScript script) + { + } public abstract void Evaluate(); @@ -148,7 +151,7 @@ namespace Artemis.Core #endregion } - public abstract class Node : CustomViewModelNode where T : CustomNodeViewModel + public abstract class Node : CustomViewModelNode where T : ICustomNodeViewModel { protected Node() { diff --git a/src/Artemis.Core/VisualScripting/NodeData.cs b/src/Artemis.Core/VisualScripting/NodeData.cs index 7d21a0438..8a13d4580 100644 --- a/src/Artemis.Core/VisualScripting/NodeData.cs +++ b/src/Artemis.Core/VisualScripting/NodeData.cs @@ -14,13 +14,13 @@ namespace Artemis.Core public string Description { get; } public string Category { get; } - private Func _create; + private Func _create; #endregion #region Constructors - internal NodeData(Plugin plugin, Type type, string name, string description, string category, Func? create) + internal NodeData(Plugin plugin, Type type, string name, string description, string category, Func? create) { this.Plugin = plugin; this.Type = type; @@ -34,7 +34,7 @@ namespace Artemis.Core #region Methods - public INode CreateNode(NodeEntity? entity) => _create(entity); + public INode CreateNode(INodeScript script, NodeEntity? entity) => _create(script, entity); #endregion } diff --git a/src/Artemis.Core/VisualScripting/NodeScript.cs b/src/Artemis.Core/VisualScripting/NodeScript.cs index ecb992895..8f675a56e 100644 --- a/src/Artemis.Core/VisualScripting/NodeScript.cs +++ b/src/Artemis.Core/VisualScripting/NodeScript.cs @@ -24,25 +24,29 @@ namespace Artemis.Core protected INode ExitNode { get; set; } public abstract Type ResultType { get; } + public object? Context { get; set; } + #endregion #region Constructors - public NodeScript(string name, string description) + public NodeScript(string name, string description, object? context = null) { this.Name = name; this.Description = description; + this.Context = context; this.Entity = new NodeScriptEntity(); NodeTypeStore.NodeTypeAdded += NodeTypeStoreOnNodeTypeChanged; NodeTypeStore.NodeTypeRemoved += NodeTypeStoreOnNodeTypeChanged; } - internal NodeScript(string name, string description, NodeScriptEntity entity) + internal NodeScript(string name, string description, NodeScriptEntity entity, object? context = null) { this.Name = name; this.Description = description; this.Entity = entity; + this.Context = context; NodeTypeStore.NodeTypeAdded += NodeTypeStoreOnNodeTypeChanged; NodeTypeStore.NodeTypeRemoved += NodeTypeStoreOnNodeTypeChanged; @@ -108,7 +112,7 @@ namespace Artemis.Core return null; // Create the node - node = nodeTypeRegistration.NodeData.CreateNode(nodeEntity); + node = nodeTypeRegistration.NodeData.CreateNode(this, nodeEntity); } else { @@ -272,8 +276,8 @@ namespace Artemis.Core #region Constructors - internal NodeScript(string name, string description, NodeScriptEntity entity) - : base(name, description, entity) + internal NodeScript(string name, string description, NodeScriptEntity entity, object? context = null) + : base(name, description, entity, context) { ExitNode = new ExitNode(name, description); AddNode(ExitNode); @@ -281,8 +285,8 @@ namespace Artemis.Core Load(); } - public NodeScript(string name, string description) - : base(name, description) + public NodeScript(string name, string description, object? context = null) + : base(name, description, context) { ExitNode = new ExitNode(name, description); AddNode(ExitNode); diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs index 08f3659e9..72f4bfb91 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs @@ -116,8 +116,6 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions if (RenderProfileElement == null) return; - RenderProfileElement.DisplayCondition ??= new NodeScript("End Result", ""); - _windowManager.ShowDialog(_nodeVmFactory.NodeScriptWindowViewModel(RenderProfileElement.DisplayCondition)); _profileEditorService.SaveSelectedProfileElement(); } diff --git a/src/Artemis.VisualScripting/Artemis.VisualScripting.csproj b/src/Artemis.VisualScripting/Artemis.VisualScripting.csproj index 93f3b3450..85b4c6b8e 100644 --- a/src/Artemis.VisualScripting/Artemis.VisualScripting.csproj +++ b/src/Artemis.VisualScripting/Artemis.VisualScripting.csproj @@ -50,6 +50,9 @@ $(DefaultXamlRuntime) Designer + + $(DefaultXamlRuntime) + $(DefaultXamlRuntime) diff --git a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPresenter.cs b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPresenter.cs index d7c4016c9..bc92fa645 100644 --- a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPresenter.cs +++ b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPresenter.cs @@ -383,7 +383,8 @@ namespace Artemis.VisualScripting.Editor.Controls if (_creationBoxParent.ContextMenu != null) _creationBoxParent.ContextMenu.IsOpen = false; - INode node = nodeData.CreateNode(null); + INode node = nodeData.CreateNode(Script, null); + node.Initialize(Script); Script.AddNode(node); InitializeNode(node, _lastRightClickLocation); diff --git a/src/Artemis.VisualScripting/Nodes/CustomViewModels/CustomNodeViewModel.cs b/src/Artemis.VisualScripting/Nodes/CustomViewModels/CustomNodeViewModel.cs new file mode 100644 index 000000000..fca28dbbb --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/CustomViewModels/CustomNodeViewModel.cs @@ -0,0 +1,34 @@ +using System.Windows; +using Artemis.Core; +using Stylet; + +namespace Artemis.VisualScripting.Nodes.CustomViewModels +{ + public abstract class CustomNodeViewModel : PropertyChangedBase, IViewAware, ICustomNodeViewModel + { + protected CustomNodeViewModel(INode node) + { + Node = node; + } + + public INode Node { get; } + + #region Implementation of IViewAware + + /// + public void AttachView(UIElement view) + { + View = view; + OnDisplay(); + } + + protected virtual void OnDisplay() + { + } + + /// + public UIElement View { get; private set; } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/CustomViewModels/DataModelNodeCustomViewModel.cs b/src/Artemis.VisualScripting/Nodes/CustomViewModels/DataModelNodeCustomViewModel.cs index 4e18dd0b1..126a1815d 100644 --- a/src/Artemis.VisualScripting/Nodes/CustomViewModels/DataModelNodeCustomViewModel.cs +++ b/src/Artemis.VisualScripting/Nodes/CustomViewModels/DataModelNodeCustomViewModel.cs @@ -1,16 +1,25 @@ using Artemis.Core; +using Artemis.Core.Modules; +using Stylet; namespace Artemis.VisualScripting.Nodes.CustomViewModels { public class DataModelNodeCustomViewModel : CustomNodeViewModel { private readonly DataModelNode _node; + private BindableCollection _modules; public DataModelNodeCustomViewModel(DataModelNode node) : base(node) { _node = node; } + public BindableCollection Modules + { + get => _modules; + set => SetAndNotify(ref _modules, value); + } + public DataModelPath DataModelPath { get => _node.DataModelPath; @@ -32,5 +41,28 @@ namespace Artemis.VisualScripting.Nodes.CustomViewModels _node.UpdateOutputPin(); } } + + public void Initialize() + { + if (Modules != null) + return; + + Modules = new BindableCollection(); + if (_node.Script.Context is Profile scriptProfile && scriptProfile.Configuration.Module != null) + Modules.Add(scriptProfile.Configuration.Module); + else if (_node.Script.Context is ProfileConfiguration profileConfiguration && profileConfiguration.Module != null) + Modules.Add(profileConfiguration.Module); + } + + #region Overrides of CustomNodeViewModel + + /// + protected override void OnDisplay() + { + Initialize(); + base.OnDisplay(); + } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/CustomViewModels/StaticDoubleValueNodeCustomViewModel.cs b/src/Artemis.VisualScripting/Nodes/CustomViewModels/StaticDoubleValueNodeCustomViewModel.cs deleted file mode 100644 index 1a0db0a2e..000000000 --- a/src/Artemis.VisualScripting/Nodes/CustomViewModels/StaticDoubleValueNodeCustomViewModel.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Artemis.Core; - -namespace Artemis.VisualScripting.Nodes.CustomViewModels -{ - public class StaticDoubleValueNodeCustomViewModel : CustomNodeViewModel - { - private readonly StaticDoubleValueNode _node; - - public StaticDoubleValueNodeCustomViewModel(StaticDoubleValueNode node) : base(node) - { - _node = node; - } - - public double Input - { - get => (double) _node.Storage; - set - { - _node.Storage = value; - OnPropertyChanged(nameof(Input)); - } - } - } -} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/CustomViewModels/StaticIntegerValueNodeCustomViewModel.cs b/src/Artemis.VisualScripting/Nodes/CustomViewModels/StaticIntegerValueNodeCustomViewModel.cs deleted file mode 100644 index 14e63812c..000000000 --- a/src/Artemis.VisualScripting/Nodes/CustomViewModels/StaticIntegerValueNodeCustomViewModel.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Artemis.Core; - -namespace Artemis.VisualScripting.Nodes.CustomViewModels -{ - public class StaticIntegerValueNodeCustomViewModel : CustomNodeViewModel - { - private readonly StaticIntegerValueNode _node; - - public StaticIntegerValueNodeCustomViewModel(StaticIntegerValueNode node) : base(node) - { - _node = node; - } - - public int Input - { - get - { - if (_node.Storage is long longInput) - return (int) longInput; - return (int) _node.Storage; - } - set - { - _node.Storage = value; - OnPropertyChanged(nameof(Input)); - } - } - } -} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/CustomViewModels/StaticStringValueNodeCustomViewModel.cs b/src/Artemis.VisualScripting/Nodes/CustomViewModels/StaticStringValueNodeCustomViewModel.cs deleted file mode 100644 index 1b6f3eae4..000000000 --- a/src/Artemis.VisualScripting/Nodes/CustomViewModels/StaticStringValueNodeCustomViewModel.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Artemis.Core; - -namespace Artemis.VisualScripting.Nodes.CustomViewModels -{ - public class StaticStringValueNodeCustomViewModel : CustomNodeViewModel - { - private readonly StaticStringValueNode _node; - - public StaticStringValueNodeCustomViewModel(StaticStringValueNode node) : base(node) - { - _node = node; - } - - public string Input - { - get => (string) _node.Storage; - set - { - _node.Storage = value; - OnPropertyChanged(nameof(Input)); - } - } - } -} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/CustomViewModels/StaticValueNodeViewModels.cs b/src/Artemis.VisualScripting/Nodes/CustomViewModels/StaticValueNodeViewModels.cs new file mode 100644 index 000000000..17b09f66e --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/CustomViewModels/StaticValueNodeViewModels.cs @@ -0,0 +1,89 @@ +using Artemis.Core; + +namespace Artemis.VisualScripting.Nodes.CustomViewModels +{ + public class StaticDoubleValueNodeCustomViewModel : CustomNodeViewModel + { + private readonly StaticDoubleValueNode _node; + + public StaticDoubleValueNodeCustomViewModel(StaticDoubleValueNode node) : base(node) + { + _node = node; + } + + public double Input + { + get => (double) _node.Storage; + set + { + _node.Storage = value; + OnPropertyChanged(nameof(Input)); + } + } + } + + public class StaticFloatValueNodeCustomViewModel : CustomNodeViewModel + { + private readonly StaticFloatValueNode _node; + + public StaticFloatValueNodeCustomViewModel(StaticFloatValueNode node) : base(node) + { + _node = node; + } + + public float Input + { + get => (float)_node.Storage; + set + { + _node.Storage = value; + OnPropertyChanged(nameof(Input)); + } + } + } + + public class StaticIntegerValueNodeCustomViewModel : CustomNodeViewModel + { + private readonly StaticIntegerValueNode _node; + + public StaticIntegerValueNodeCustomViewModel(StaticIntegerValueNode node) : base(node) + { + _node = node; + } + + public int Input + { + get + { + if (_node.Storage is long longInput) + return (int)longInput; + return (int)_node.Storage; + } + set + { + _node.Storage = value; + OnPropertyChanged(nameof(Input)); + } + } + } + + public class StaticStringValueNodeCustomViewModel : CustomNodeViewModel + { + private readonly StaticStringValueNode _node; + + public StaticStringValueNodeCustomViewModel(StaticStringValueNode node) : base(node) + { + _node = node; + } + + public string Input + { + get => (string)_node.Storage; + set + { + _node.Storage = value; + OnPropertyChanged(nameof(Input)); + } + } + } +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/CustomViews/DataModelNodeCustomView.xaml b/src/Artemis.VisualScripting/Nodes/CustomViews/DataModelNodeCustomView.xaml index 4ced4397a..e1a1c9d39 100644 --- a/src/Artemis.VisualScripting/Nodes/CustomViews/DataModelNodeCustomView.xaml +++ b/src/Artemis.VisualScripting/Nodes/CustomViews/DataModelNodeCustomView.xaml @@ -8,5 +8,5 @@ xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> - + diff --git a/src/Artemis.VisualScripting/Nodes/CustomViews/StaticFloatValueNodeCustomView.xaml b/src/Artemis.VisualScripting/Nodes/CustomViews/StaticFloatValueNodeCustomView.xaml new file mode 100644 index 000000000..7cd2b5ad7 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/CustomViews/StaticFloatValueNodeCustomView.xaml @@ -0,0 +1,12 @@ + + + diff --git a/src/Artemis.VisualScripting/Nodes/DataModelNode.cs b/src/Artemis.VisualScripting/Nodes/DataModelNode.cs index 53b172eb1..64b28a169 100644 --- a/src/Artemis.VisualScripting/Nodes/DataModelNode.cs +++ b/src/Artemis.VisualScripting/Nodes/DataModelNode.cs @@ -13,18 +13,21 @@ namespace Artemis.VisualScripting.Nodes { } + public INodeScript Script { get; private set; } public OutputPin Output { get; private set; } public DataModelPath DataModelPath { get; set; } - public override void Initialize() + public override void Initialize(INodeScript script) { - if (Storage is not DataModelPathEntity pathEntity) + Script = script; + + if (Storage is not DataModelPathEntity pathEntity) return; DataModelPath = new DataModelPath(null, pathEntity); UpdateOutputPin(); } - + public override void Evaluate() { if (DataModelPath.IsValid && Output != null) diff --git a/src/Artemis.VisualScripting/Nodes/StaticValueNodes.cs b/src/Artemis.VisualScripting/Nodes/StaticValueNodes.cs index eadf03c79..4d37fb320 100644 --- a/src/Artemis.VisualScripting/Nodes/StaticValueNodes.cs +++ b/src/Artemis.VisualScripting/Nodes/StaticValueNodes.cs @@ -29,7 +29,7 @@ namespace Artemis.VisualScripting.Nodes Output.Value = Storage as int? ?? 0; } - public override void Initialize() => Storage ??= 0; + public override void Initialize(INodeScript script) => Storage ??= 0; #endregion } @@ -60,7 +60,38 @@ namespace Artemis.VisualScripting.Nodes Output.Value = Storage as double? ?? 0.0; } - public override void Initialize() => Storage ??= 0.0; + public override void Initialize(INodeScript script) => Storage ??= 0.0; + + #endregion + } + + [Node("Float-Value", "Outputs a configurable float value.")] + public class StaticFloatValueNode : Node + { + #region Properties & Fields + + public OutputPin Output { get; } + + #endregion + + #region Constructors + + public StaticFloatValueNode() + : base("Float", "Outputs a configurable float value.") + { + Output = CreateOutputPin(); + } + + #endregion + + #region Methods + + public override void Evaluate() + { + Output.Value = Storage as float? ?? 0.0f; + } + + public override void Initialize(INodeScript script) => Storage ??= 0.0f; #endregion } diff --git a/src/Artemis.VisualScripting/Nodes/SumNode.cs b/src/Artemis.VisualScripting/Nodes/SumNode.cs index 555f6a7f1..27cf67955 100644 --- a/src/Artemis.VisualScripting/Nodes/SumNode.cs +++ b/src/Artemis.VisualScripting/Nodes/SumNode.cs @@ -35,6 +35,38 @@ namespace Artemis.VisualScripting.Nodes #endregion } + [Node("Sum (Float)", "Sums the connected float values.")] + public class SumFloatsNode : Node + { + #region Properties & Fields + + public InputPinCollection Values { get; } + + public OutputPin Sum { get; } + + #endregion + + #region Constructors + + public SumFloatsNode() + : base("Sum", "Sums the connected float values.") + { + Values = CreateInputPinCollection("Values", 2); + Sum = CreateOutputPin("Sum"); + } + + #endregion + + #region Methods + + public override void Evaluate() + { + Sum.Value = Values.Values.Sum(); + } + + #endregion + } + [Node("Sum (Double)", "Sums the connected double values.")] public class SumDoublesNode : Node { From a4667fdc037d8ab3d238eb376ef1248d2270ab13 Mon Sep 17 00:00:00 2001 From: Robert Date: Sat, 21 Aug 2021 13:51:25 +0200 Subject: [PATCH 17/47] Profiles - Only load nodes after the entire profile structure loaded Profiles - Added API to get all render elements Nodes - Added layer property node --- .../Profile/DataBindings/DataBinding.cs | 4 + .../DataBindings/DataBindingRegistration.cs | 2 +- .../Profile/DataBindings/IDataBinding.cs | 5 + src/Artemis.Core/Models/Profile/Profile.cs | 17 ++- .../Models/Profile/ProfileElement.cs | 79 +++++++---- .../Models/Profile/RenderProfileElement.cs | 17 ++- .../Services/Storage/ProfileService.cs | 14 +- .../Visualization/ProfileViewModel.cs | 8 +- .../LayerPropertyNodeCustomViewModel.cs | 80 +++++++++++ .../LayerPropertyNodeCustomView.xaml | 18 +++ .../Nodes/LayerPropertyNode.cs | 130 ++++++++++++++++++ 11 files changed, 317 insertions(+), 57 deletions(-) create mode 100644 src/Artemis.VisualScripting/Nodes/CustomViewModels/LayerPropertyNodeCustomViewModel.cs create mode 100644 src/Artemis.VisualScripting/Nodes/CustomViews/LayerPropertyNodeCustomView.xaml create mode 100644 src/Artemis.VisualScripting/Nodes/LayerPropertyNode.cs diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs index 0cf5fc9fb..e2392c79e 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs @@ -242,7 +242,11 @@ namespace Artemis.Core EasingTime = Entity.EasingTime; EasingFunction = (Easings.Functions) Entity.EasingFunction; + } + /// + public void LoadNodeScript() + { Script.Dispose(); Script = Entity.NodeScript != null ? new NodeScript(GetScriptName(), "The value to put into the data binding", Entity.NodeScript, LayerProperty.ProfileElement.Profile) diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs index cab335a43..e7d5ca1d1 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs @@ -67,7 +67,7 @@ namespace Artemis.Core DataBinding = new DataBinding(LayerProperty, dataBinding); return DataBinding; } - + /// public void ClearDataBinding() { diff --git a/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs index 1f54ca146..3c7c995bd 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs @@ -19,5 +19,10 @@ namespace Artemis.Core /// Applies the data binding to the layer property /// void Apply(); + + /// + /// If the data binding is enabled, loads the node script for that data binding + /// + void LoadNodeScript(); } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Profile.cs b/src/Artemis.Core/Models/Profile/Profile.cs index c0c3a2528..a9090e12d 100644 --- a/src/Artemis.Core/Models/Profile/Profile.cs +++ b/src/Artemis.Core/Models/Profile/Profile.cs @@ -206,12 +206,10 @@ namespace Artemis.Core } } + List renderElements = GetAllRenderElements(); + if (ProfileEntity.LastSelectedProfileElement != Guid.Empty) - { - LastSelectedProfileElement = GetAllFolders().FirstOrDefault(f => f.EntityId == ProfileEntity.LastSelectedProfileElement); - if (LastSelectedProfileElement == null) - LastSelectedProfileElement = GetAllLayers().FirstOrDefault(f => f.EntityId == ProfileEntity.LastSelectedProfileElement); - } + LastSelectedProfileElement = renderElements.FirstOrDefault(f => f.EntityId == ProfileEntity.LastSelectedProfileElement); else LastSelectedProfileElement = null; @@ -219,6 +217,10 @@ namespace Artemis.Core scriptConfiguration.Script?.Dispose(); ScriptConfigurations.Clear(); ScriptConfigurations.AddRange(ProfileEntity.ScriptConfigurations.Select(e => new ScriptConfiguration(e))); + + // Load node scripts last since they may rely on the profile structure being in place + foreach (RenderProfileElement renderProfileElement in renderElements) + renderProfileElement.LoadNodeScript(); } internal override void Save() @@ -253,10 +255,7 @@ namespace Artemis.Core /// public override IEnumerable GetBrokenHierarchy() { - foreach (IBreakableModel breakableModel in GetAllFolders().SelectMany(folders => folders.GetBrokenHierarchy())) - yield return breakableModel; - foreach (IBreakableModel breakableModel in GetAllLayers().SelectMany(layer => layer.GetBrokenHierarchy())) - yield return breakableModel; + return GetAllRenderElements().SelectMany(folders => folders.GetBrokenHierarchy()); } #endregion diff --git a/src/Artemis.Core/Models/Profile/ProfileElement.cs b/src/Artemis.Core/Models/Profile/ProfileElement.cs index 81537b0ba..a36dea794 100644 --- a/src/Artemis.Core/Models/Profile/ProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/ProfileElement.cs @@ -99,6 +99,13 @@ namespace Artemis.Core /// public bool Disposed { get; protected set; } + #region Overrides of BreakableModel + + /// + public override string BrokenDisplayName => Name ?? GetType().Name; + + #endregion + /// /// Updates the element /// @@ -121,13 +128,6 @@ namespace Artemis.Core return $"{nameof(EntityId)}: {EntityId}, {nameof(Order)}: {Order}, {nameof(Name)}: {Name}"; } - #region Overrides of BreakableModel - - /// - public override string BrokenDisplayName => Name ?? GetType().Name; - - #endregion - #region Hierarchy /// @@ -147,7 +147,9 @@ namespace Artemis.Core // Add to the end of the list if (order == null) + { ChildrenList.Add(child); + } // Insert at the given index else { @@ -191,6 +193,27 @@ namespace Artemis.Core ChildrenList[index].Order = index; } + /// + /// Returns a flattened list of all child render elements + /// + /// + public List GetAllRenderElements() + { + if (Disposed) + throw new ObjectDisposedException(GetType().Name); + + List elements = new(); + foreach (RenderProfileElement childElement in Children.Where(c => c is RenderProfileElement).Cast()) + { + // Add all folders in this element + elements.Add(childElement); + // Add all folders in folders inside this element + elements.AddRange(childElement.GetAllRenderElements()); + } + + return elements; + } + /// /// Returns a flattened list of all child folders /// @@ -240,27 +263,6 @@ namespace Artemis.Core internal abstract void Load(); internal abstract void Save(); - #endregion - - #region IDisposable - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Disposes the profile element - /// - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - } - } - #endregion #region Events @@ -292,5 +294,26 @@ namespace Artemis.Core } #endregion + + #region IDisposable + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Disposes the profile element + /// + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + } + } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs index 7003dd985..f6a87f5b0 100644 --- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs @@ -70,10 +70,6 @@ namespace Artemis.Core internal void LoadRenderElement() { - DisplayCondition = RenderElementEntity.NodeScript != null - ? new NodeScript($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active", RenderElementEntity.NodeScript, Profile) - : new NodeScript($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active", Profile); - Timeline = RenderElementEntity.Timeline != null ? new Timeline(RenderElementEntity.Timeline) : new Timeline(); @@ -111,6 +107,19 @@ namespace Artemis.Core Timeline?.Save(); } + internal void LoadNodeScript() + { + DisplayCondition = RenderElementEntity.NodeScript != null + ? new NodeScript($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active", RenderElementEntity.NodeScript, Profile) + : new NodeScript($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active", Profile); + + foreach (ILayerProperty layerProperty in GetAllLayerProperties()) + { + foreach (IDataBindingRegistration dataBindingRegistration in layerProperty.GetAllDataBindingRegistrations()) + dataBindingRegistration.GetDataBinding()?.LoadNodeScript(); + } + } + internal void OnLayerEffectsUpdated() { LayerEffectsUpdated?.Invoke(this, EventArgs.Empty); diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index 3f532faf3..b6221c19f 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -438,10 +438,8 @@ namespace Artemis.Core.Services profile.Save(); if (includeChildren) { - foreach (Folder folder in profile.GetAllFolders()) - folder.Save(); - foreach (Layer layer in profile.GetAllLayers()) - layer.Save(); + foreach (RenderProfileElement child in profile.GetAllRenderElements()) + child.Save(); } // If there are no changes, don't bother saving @@ -599,11 +597,9 @@ namespace Artemis.Core.Services profile.Save(); - foreach (Folder folder in profile.GetAllFolders()) - folder.Save(); - foreach (Layer layer in profile.GetAllLayers()) - layer.Save(); - + foreach (RenderProfileElement renderProfileElement in profile.GetAllRenderElements()) + renderProfileElement.Save(); + _logger.Debug("Adapt profile - Saving " + profile); profile.RedoStack.Clear(); profile.UndoStack.Push(memento); diff --git a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs index 6f4fea3d0..b0549cdd7 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs @@ -313,15 +313,11 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization if (SuspendedEditing || !_settingsService.GetSetting("ProfileEditor.AlwaysApplyDataBindings", true).Value || _profileEditorService.SelectedProfile == null) return; - foreach (IDataBindingRegistration dataBindingRegistration in _profileEditorService.SelectedProfile.GetAllFolders() + foreach (IDataBindingRegistration dataBindingRegistration in _profileEditorService.SelectedProfile.GetAllRenderElements() .SelectMany(f => f.GetAllLayerProperties(), (f, p) => p) .SelectMany(p => p.GetAllDataBindingRegistrations())) dataBindingRegistration.GetDataBinding()?.UpdateWithDelta(delta); - foreach (IDataBindingRegistration dataBindingRegistration in _profileEditorService.SelectedProfile.GetAllLayers() - .SelectMany(f => f.GetAllLayerProperties(), (f, p) => p) - .SelectMany(p => p.GetAllDataBindingRegistrations())) - dataBindingRegistration.GetDataBinding()?.UpdateWithDelta(delta); - + // TODO: Only update when there are data bindings if (!_profileEditorService.Playing) _profileEditorService.UpdateProfilePreview(); diff --git a/src/Artemis.VisualScripting/Nodes/CustomViewModels/LayerPropertyNodeCustomViewModel.cs b/src/Artemis.VisualScripting/Nodes/CustomViewModels/LayerPropertyNodeCustomViewModel.cs new file mode 100644 index 000000000..edf84d7e1 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/CustomViewModels/LayerPropertyNodeCustomViewModel.cs @@ -0,0 +1,80 @@ +using System.Collections.Generic; +using System.Linq; +using Artemis.Core; +using Stylet; + +namespace Artemis.VisualScripting.Nodes.CustomViewModels +{ + public class LayerPropertyNodeCustomViewModel : CustomNodeViewModel + { + private readonly LayerPropertyNode _node; + + private RenderProfileElement _selectedProfileElement; + private ILayerProperty _selectedLayerProperty; + + public LayerPropertyNodeCustomViewModel(LayerPropertyNode node) : base(node) + { + _node = node; + } + + public BindableCollection ProfileElements { get; } = new(); + + public RenderProfileElement SelectedProfileElement + { + get => _selectedProfileElement; + set + { + if (!SetAndNotify(ref _selectedProfileElement, value)) return; + _node.ChangeProfileElement(_selectedProfileElement); + GetLayerProperties(); + } + } + + public BindableCollection LayerProperties { get; } = new(); + + public ILayerProperty SelectedLayerProperty + { + get => _selectedLayerProperty; + set + { + if (!SetAndNotify(ref _selectedLayerProperty, value)) return; + _node.ChangeLayerProperty(_selectedLayerProperty); + } + } + + private void GetProfileElements() + { + ProfileElements.Clear(); + if (_node.Script.Context is not Profile profile) + return; + + List elements = new(profile.GetAllRenderElements()); + + ProfileElements.AddRange(elements.OrderBy(e => e.Order)); + _selectedProfileElement = _node.ProfileElement; + NotifyOfPropertyChange(nameof(SelectedProfileElement)); + } + + private void GetLayerProperties() + { + LayerProperties.Clear(); + if (_node.ProfileElement == null) + return; + + LayerProperties.AddRange(_node.ProfileElement.GetAllLayerProperties().Where(l => l.GetAllDataBindingRegistrations().Any())); + _selectedLayerProperty = _node.LayerProperty; + NotifyOfPropertyChange(nameof(SelectedLayerProperty)); + } + + #region Overrides of CustomNodeViewModel + + /// + protected override void OnDisplay() + { + GetProfileElements(); + GetLayerProperties(); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/CustomViews/LayerPropertyNodeCustomView.xaml b/src/Artemis.VisualScripting/Nodes/CustomViews/LayerPropertyNodeCustomView.xaml new file mode 100644 index 000000000..42be19cb8 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/CustomViews/LayerPropertyNodeCustomView.xaml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/LayerPropertyNode.cs b/src/Artemis.VisualScripting/Nodes/LayerPropertyNode.cs new file mode 100644 index 000000000..64f75d022 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/LayerPropertyNode.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Artemis.Core; +using Artemis.VisualScripting.Nodes.CustomViewModels; + +namespace Artemis.VisualScripting.Nodes +{ + [Node("Layer/Folder Property", "Outputs the property of a selected layer or folder")] + public class LayerPropertyNode : Node + { + private readonly object _layerPropertyLock = new(); + + public INodeScript Script { get; private set; } + public RenderProfileElement ProfileElement { get; private set; } + public ILayerProperty LayerProperty { get; private set; } + + public override void Evaluate() + { + lock (_layerPropertyLock) + { + // In this case remove the pins so no further evaluations occur + if (LayerProperty == null) + { + CreatePins(); + return; + } + + List list = LayerProperty.GetAllDataBindingRegistrations(); + int index = 0; + foreach (IPin pin in Pins) + { + OutputPin outputPin = (OutputPin) pin; + IDataBindingRegistration dataBindingRegistration = list[index]; + index++; + + // TODO: Is this really non-nullable? + outputPin.Value = dataBindingRegistration.GetValue(); + } + } + } + + public override void Initialize(INodeScript script) + { + Script = script; + + if (script.Context is Profile profile) + profile.ChildRemoved += ProfileOnChildRemoved; + + LoadLayerProperty(); + } + + public void LoadLayerProperty() + { + lock (_layerPropertyLock) + { + if (Script.Context is not Profile profile) + return; + if (Storage is not LayerPropertyNodeEntity entity) + return; + + RenderProfileElement element = profile.GetAllRenderElements().FirstOrDefault(l => l.EntityId == entity.ElementId); + + ProfileElement = element; + LayerProperty = element?.GetAllLayerProperties().FirstOrDefault(p => p.Path == entity.PropertyPath); + CreatePins(); + } + } + + public void ChangeProfileElement(RenderProfileElement profileElement) + { + lock (_layerPropertyLock) + { + ProfileElement = profileElement; + LayerProperty = null; + + Storage = new LayerPropertyNodeEntity + { + ElementId = ProfileElement?.EntityId ?? Guid.Empty, + PropertyPath = null + }; + + CreatePins(); + } + } + + public void ChangeLayerProperty(ILayerProperty layerProperty) + { + lock (_layerPropertyLock) + { + LayerProperty = layerProperty; + + Storage = new LayerPropertyNodeEntity + { + ElementId = ProfileElement?.EntityId ?? Guid.Empty, + PropertyPath = LayerProperty?.Path + }; + + CreatePins(); + } + } + + private void CreatePins() + { + while (Pins.Any()) + RemovePin((Pin) Pins.First()); + + if (LayerProperty == null) + return; + + foreach (IDataBindingRegistration dataBindingRegistration in LayerProperty.GetAllDataBindingRegistrations()) + CreateOutputPin(dataBindingRegistration.ValueType, dataBindingRegistration.DisplayName); + } + + private void ProfileOnChildRemoved(object? sender, EventArgs e) + { + if (Script.Context is not Profile profile) + return; + + if (!profile.GetAllRenderElements().Contains(ProfileElement)) + ChangeProfileElement(null); + } + } + + public class LayerPropertyNodeEntity + { + public Guid ElementId { get; set; } + public string PropertyPath { get; set; } + } +} \ No newline at end of file From 405d5b756cebf250cb8b8130c482913302a41f19 Mon Sep 17 00:00:00 2001 From: Robert Date: Sat, 21 Aug 2021 15:33:55 +0200 Subject: [PATCH 18/47] Nodes - Serialize node storage ourselves to ignore exceptions --- src/Artemis.Core/Services/NodeService.cs | 10 +++++++++- src/Artemis.Core/VisualScripting/Interfaces/INode.cs | 2 +- src/Artemis.Core/VisualScripting/NodeScript.cs | 4 ++-- .../Entities/Profile/Nodes/NodeEntity.cs | 2 +- src/Artemis.VisualScripting/Nodes/StaticValueNodes.cs | 3 +++ 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/Artemis.Core/Services/NodeService.cs b/src/Artemis.Core/Services/NodeService.cs index fd09d8de5..82cf2d4f7 100644 --- a/src/Artemis.Core/Services/NodeService.cs +++ b/src/Artemis.Core/Services/NodeService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Reflection; using Artemis.Storage.Entities.Profile.Nodes; +using Newtonsoft.Json; using Ninject; using Ninject.Parameters; using SkiaSharp; @@ -73,7 +74,14 @@ namespace Artemis.Core.Services { node.X = entity.X; node.Y = entity.Y; - node.Storage = entity.Storage; + try + { + node.Storage = CoreJson.DeserializeObject(entity.Storage, true); + } + catch + { + // ignored + } } if (node is CustomViewModelNode customViewModelNode) diff --git a/src/Artemis.Core/VisualScripting/Interfaces/INode.cs b/src/Artemis.Core/VisualScripting/Interfaces/INode.cs index f17fd3352..5f415d987 100644 --- a/src/Artemis.Core/VisualScripting/Interfaces/INode.cs +++ b/src/Artemis.Core/VisualScripting/Interfaces/INode.cs @@ -12,7 +12,7 @@ namespace Artemis.Core public double X { get; set; } public double Y { get; set; } - public object Storage { get; set; } + public object? Storage { get; set; } public IReadOnlyCollection Pins { get; } public IReadOnlyCollection PinCollections { get; } diff --git a/src/Artemis.Core/VisualScripting/NodeScript.cs b/src/Artemis.Core/VisualScripting/NodeScript.cs index 8f675a56e..ba35ed922 100644 --- a/src/Artemis.Core/VisualScripting/NodeScript.cs +++ b/src/Artemis.Core/VisualScripting/NodeScript.cs @@ -96,7 +96,7 @@ namespace Artemis.Core continue; nodes.Add(entityNode.Id, node); } - + LoadConnections(nodes); _nodes.Clear(); @@ -179,7 +179,7 @@ namespace Artemis.Core Type = node.GetType().Name, X = node.X, Y = node.Y, - Storage = node.Storage, + Storage = CoreJson.SerializeObject(node.Storage, true), Name = node.Name, Description = node.Description, IsExitNode = node.IsExitNode diff --git a/src/Artemis.Storage/Entities/Profile/Nodes/NodeEntity.cs b/src/Artemis.Storage/Entities/Profile/Nodes/NodeEntity.cs index 6f33425f0..8a99bb8f0 100644 --- a/src/Artemis.Storage/Entities/Profile/Nodes/NodeEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/Nodes/NodeEntity.cs @@ -19,7 +19,7 @@ namespace Artemis.Storage.Entities.Profile.Nodes public bool IsExitNode { get; set; } public double X { get; set; } public double Y { get; set; } - public object Storage { get; set; } + public string Storage { get; set; } public List PinCollections { get; set; } } diff --git a/src/Artemis.VisualScripting/Nodes/StaticValueNodes.cs b/src/Artemis.VisualScripting/Nodes/StaticValueNodes.cs index 4d37fb320..c144e1847 100644 --- a/src/Artemis.VisualScripting/Nodes/StaticValueNodes.cs +++ b/src/Artemis.VisualScripting/Nodes/StaticValueNodes.cs @@ -88,6 +88,9 @@ namespace Artemis.VisualScripting.Nodes public override void Evaluate() { + if (Storage is double doubleValue) + Storage = (float) doubleValue; + Output.Value = Storage as float? ?? 0.0f; } From 7422a9667d6240749474951d97f0f416c9c3bfd3 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sat, 21 Aug 2021 21:48:01 +0200 Subject: [PATCH 19/47] VisualScripting: Fixed Preview; Added auto-fit mode --- .../VisualScripting/Interfaces/INodeScript.cs | 5 +- .../VisualScripting/Interfaces/IPin.cs | 3 + .../Interfaces/IPinCollection.cs | 3 + src/Artemis.Core/VisualScripting/Node.cs | 32 ++-- .../VisualScripting/NodeScript.cs | 11 ++ src/Artemis.Core/VisualScripting/Pin.cs | 16 ++ .../VisualScripting/PinCollection.cs | 21 ++- .../DisplayConditionsView.xaml | 5 +- .../Editor/Controls/VisualScriptEditor.cs | 8 - .../Editor/Controls/VisualScriptPresenter.cs | 163 +++++++++++++++--- .../Editor/Controls/Wrapper/VisualScript.cs | 84 ++++++++- .../Controls/Wrapper/VisualScriptNode.cs | 44 ++++- .../Controls/Wrapper/VisualScriptPin.cs | 30 +++- .../Wrapper/VisualScriptPinCollection.cs | 33 +++- 14 files changed, 372 insertions(+), 86 deletions(-) diff --git a/src/Artemis.Core/VisualScripting/Interfaces/INodeScript.cs b/src/Artemis.Core/VisualScripting/Interfaces/INodeScript.cs index 76a6df648..dec3cab46 100644 --- a/src/Artemis.Core/VisualScripting/Interfaces/INodeScript.cs +++ b/src/Artemis.Core/VisualScripting/Interfaces/INodeScript.cs @@ -15,7 +15,10 @@ namespace Artemis.Core Type ResultType { get; } object? Context { get; set; } - + + event EventHandler? NodeAdded; + event EventHandler? NodeRemoved; + void Run(); void AddNode(INode node); void RemoveNode(INode node); diff --git a/src/Artemis.Core/VisualScripting/Interfaces/IPin.cs b/src/Artemis.Core/VisualScripting/Interfaces/IPin.cs index aef3352cf..0ea3179e4 100644 --- a/src/Artemis.Core/VisualScripting/Interfaces/IPin.cs +++ b/src/Artemis.Core/VisualScripting/Interfaces/IPin.cs @@ -16,6 +16,9 @@ namespace Artemis.Core bool IsEvaluated { get; set; } + event EventHandler PinConnected; + event EventHandler PinDisconnected; + void ConnectTo(IPin pin); void DisconnectFrom(IPin pin); } diff --git a/src/Artemis.Core/VisualScripting/Interfaces/IPinCollection.cs b/src/Artemis.Core/VisualScripting/Interfaces/IPinCollection.cs index 410f269ba..266c31338 100644 --- a/src/Artemis.Core/VisualScripting/Interfaces/IPinCollection.cs +++ b/src/Artemis.Core/VisualScripting/Interfaces/IPinCollection.cs @@ -9,6 +9,9 @@ namespace Artemis.Core PinDirection Direction { get; } Type Type { get; } + event EventHandler PinAdded; + event EventHandler PinRemoved; + IPin AddPin(); bool Remove(IPin pin); } diff --git a/src/Artemis.Core/VisualScripting/Node.cs b/src/Artemis.Core/VisualScripting/Node.cs index 5b504a8ec..ac6b4da8a 100644 --- a/src/Artemis.Core/VisualScripting/Node.cs +++ b/src/Artemis.Core/VisualScripting/Node.cs @@ -1,18 +1,14 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Linq; namespace Artemis.Core { public abstract class Node : CorePropertyChanged, INode { - public event EventHandler Resetting; - #region Properties & Fields private string _name; - public string Name { get => _name; @@ -20,7 +16,6 @@ namespace Artemis.Core } private string _description; - public string Description { get => _description; @@ -28,7 +23,6 @@ namespace Artemis.Core } private double _x; - public double X { get => _x; @@ -36,7 +30,6 @@ namespace Artemis.Core } private double _y; - public double Y { get => _y; @@ -44,7 +37,6 @@ namespace Artemis.Core } private object? _storage; - public object? Storage { get => _storage; @@ -61,11 +53,16 @@ namespace Artemis.Core #endregion + #region Events + + public event EventHandler Resetting; + + #endregion + #region Construtors protected Node() - { - } + { } protected Node(string name, string description) { @@ -138,8 +135,7 @@ namespace Artemis.Core } public virtual void Initialize(INodeScript script) - { - } + { } public abstract void Evaluate(); @@ -154,12 +150,10 @@ namespace Artemis.Core public abstract class Node : CustomViewModelNode where T : ICustomNodeViewModel { protected Node() - { - } + { } protected Node(string name, string description) : base(name, description) - { - } + { } public override Type CustomViewModelType => typeof(T); public T CustomViewModel => (T) BaseCustomViewModel!; @@ -169,13 +163,11 @@ namespace Artemis.Core { /// protected CustomViewModelNode() - { - } + { } /// protected CustomViewModelNode(string name, string description) : base(name, description) - { - } + { } public abstract Type CustomViewModelType { get; } public object? BaseCustomViewModel { get; set; } diff --git a/src/Artemis.Core/VisualScripting/NodeScript.cs b/src/Artemis.Core/VisualScripting/NodeScript.cs index ba35ed922..0094d8daa 100644 --- a/src/Artemis.Core/VisualScripting/NodeScript.cs +++ b/src/Artemis.Core/VisualScripting/NodeScript.cs @@ -28,6 +28,13 @@ namespace Artemis.Core #endregion + #region Events + + public event EventHandler? NodeAdded; + public event EventHandler? NodeRemoved; + + #endregion + #region Constructors public NodeScript(string name, string description, object? context = null) @@ -67,11 +74,15 @@ namespace Artemis.Core public void AddNode(INode node) { _nodes.Add(node); + + NodeAdded?.Invoke(this, node); } public void RemoveNode(INode node) { _nodes.Remove(node); + + NodeRemoved?.Invoke(this, node); } public void Dispose() diff --git a/src/Artemis.Core/VisualScripting/Pin.cs b/src/Artemis.Core/VisualScripting/Pin.cs index c1192b53c..be8f3f63d 100644 --- a/src/Artemis.Core/VisualScripting/Pin.cs +++ b/src/Artemis.Core/VisualScripting/Pin.cs @@ -27,6 +27,13 @@ namespace Artemis.Core #endregion + #region Events + + public event EventHandler PinConnected; + public event EventHandler PinDisconnected; + + #endregion + #region Constructors protected Pin(INode node, string name = "") @@ -46,18 +53,27 @@ namespace Artemis.Core { _connectedTo.Add(pin); OnPropertyChanged(nameof(ConnectedTo)); + + PinConnected?.Invoke(this, pin); } public void DisconnectFrom(IPin pin) { _connectedTo.Remove(pin); OnPropertyChanged(nameof(ConnectedTo)); + + PinDisconnected?.Invoke(this, pin); } public void DisconnectAll() { + List connectedPins = new(_connectedTo); + _connectedTo.Clear(); OnPropertyChanged(nameof(ConnectedTo)); + + foreach (IPin pin in connectedPins) + PinDisconnected?.Invoke(this, pin); } private void OnNodeResetting(object sender, EventArgs e) diff --git a/src/Artemis.Core/VisualScripting/PinCollection.cs b/src/Artemis.Core/VisualScripting/PinCollection.cs index 469abf5dc..4bce68cc1 100644 --- a/src/Artemis.Core/VisualScripting/PinCollection.cs +++ b/src/Artemis.Core/VisualScripting/PinCollection.cs @@ -20,6 +20,13 @@ namespace Artemis.Core #endregion + #region Events + + public event EventHandler PinAdded; + public event EventHandler PinRemoved; + + #endregion + #region Constructors protected PinCollection(INode node, string name, int initialCount) @@ -35,14 +42,26 @@ namespace Artemis.Core #region Methods + public IPin AddPin() { IPin pin = CreatePin(); _pins.Add(pin); + + PinAdded?.Invoke(this, pin); + return pin; } - public bool Remove(IPin pin) => _pins.Remove(pin); + public bool Remove(IPin pin) + { + bool removed = _pins.Remove(pin); + + if (removed) + PinRemoved?.Invoke(this, pin); + + return removed; + } protected abstract IPin CreatePin(); diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml index 16a6b7000..44bbe92a3 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml @@ -43,8 +43,9 @@ - + diff --git a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptEditor.cs b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptEditor.cs index 82977bcf5..8af40eb0b 100644 --- a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptEditor.cs +++ b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptEditor.cs @@ -7,10 +7,6 @@ namespace Artemis.VisualScripting.Editor.Controls { public class VisualScriptEditor : Control { - #region Properties & Fields - - #endregion - #region Dependency Properties public static readonly DependencyProperty ScriptProperty = DependencyProperty.Register( @@ -32,9 +28,5 @@ namespace Artemis.VisualScripting.Editor.Controls } #endregion - - #region Methods - - #endregion } } diff --git a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPresenter.cs b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPresenter.cs index bc92fa645..0b939b2a3 100644 --- a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPresenter.cs +++ b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPresenter.cs @@ -6,6 +6,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; +using System.Windows.Threading; using Artemis.Core; using Artemis.VisualScripting.Editor.Controls.Wrapper; using Artemis.VisualScripting.ViewModel; @@ -32,11 +33,13 @@ namespace Artemis.VisualScripting.Editor.Controls #region Properties & Fields + private bool _fitPending = false; private Canvas _canvas; private ItemsControl _nodeList; private ItemsControl _cableList; private Border _selectionBorder; private TranslateTransform _canvasViewPortTransform; + private ScaleTransform _canvasViewPortScale; private Panel _creationBoxParent; private Vector _viewportCenter = new(0, 0); @@ -121,7 +124,7 @@ namespace Artemis.VisualScripting.Editor.Controls } public static readonly DependencyProperty GridSizeProperty = DependencyProperty.Register( - "GridSize", typeof(int), typeof(VisualScriptPresenter), new PropertyMetadata(12)); + "GridSize", typeof(int), typeof(VisualScriptPresenter), new PropertyMetadata(24)); public int GridSize { @@ -138,6 +141,15 @@ namespace Artemis.VisualScripting.Editor.Controls set => SetValue(SurfaceSizeProperty, value); } + public static readonly DependencyProperty AutoFitScriptProperty = DependencyProperty.Register( + "AutoFitScript", typeof(bool), typeof(VisualScriptPresenter), new PropertyMetadata(false, AutoFitScriptChanged)); + + public bool AutoFitScript + { + get => (bool)GetValue(AutoFitScriptProperty); + set => SetValue(AutoFitScriptProperty, value); + } + #endregion #region Constructors @@ -163,6 +175,7 @@ namespace Artemis.VisualScripting.Editor.Controls _canvas.AllowDrop = true; + _canvas.LayoutTransform = _canvasViewPortScale = new ScaleTransform(MaxScale, MaxScale); _canvas.RenderTransform = _canvasViewPortTransform = new TranslateTransform(0, 0); _canvas.MouseLeftButtonDown += OnCanvasMouseLeftButtonDown; _canvas.MouseLeftButtonUp += OnCanvasMouseLeftButtonUp; @@ -177,11 +190,22 @@ namespace Artemis.VisualScripting.Editor.Controls _cableList.ItemsSource = VisualScript?.Cables; } + private static void AutoFitScriptChanged(DependencyObject d, DependencyPropertyChangedEventArgs args) + { + if (d is not VisualScriptPresenter scriptPresenter) return; + + if ((args.NewValue as bool?) == true) + scriptPresenter.FitScript(); + } + private void OnSizeChanged(object sender, SizeChangedEventArgs args) { if (sender is not VisualScriptPresenter scriptPresenter) return; - scriptPresenter.UpdatePanning(); + if (AutoFitScript) + scriptPresenter.FitScript(); + else + scriptPresenter.UpdatePanning(); } private static void ScriptChanged(DependencyObject d, DependencyPropertyChangedEventArgs args) @@ -194,28 +218,33 @@ namespace Artemis.VisualScripting.Editor.Controls private void ScriptChanged(VisualScript newScript) { if (VisualScript != null) + { VisualScript.PropertyChanged -= OnVisualScriptPropertyChanged; + VisualScript.NodeMoved -= OnVisualScriptNodeMoved; + VisualScript.NodeCollectionChanged -= OnVisualScriptNodeCollectionChanged; + } VisualScript = newScript; if (VisualScript != null) { VisualScript.PropertyChanged += OnVisualScriptPropertyChanged; + VisualScript.NodeMoved += OnVisualScriptNodeMoved; + VisualScript.NodeCollectionChanged += OnVisualScriptNodeCollectionChanged; if (_nodeList != null) _nodeList.ItemsSource = VisualScript?.Nodes; if (_cableList != null) _cableList.ItemsSource = VisualScript?.Cables; - - VisualScript.Nodes.Clear(); - foreach (INode node in VisualScript.Script.Nodes) - InitializeNode(node); } VisualScript?.RecreateCables(); - CenterAt(new Vector(0, 0)); + if (AutoFitScript) + FitScript(); + else + CenterAt(new Vector(0, 0)); } private void OnVisualScriptPropertyChanged(object sender, PropertyChangedEventArgs args) @@ -225,6 +254,18 @@ namespace Artemis.VisualScripting.Editor.Controls _cableList.ItemsSource = VisualScript.Cables; } + private void OnVisualScriptNodeMoved(object sender, EventArgs args) + { + if (AutoFitScript) + FitScript(); + } + + private void OnVisualScriptNodeCollectionChanged(object? sender, EventArgs e) + { + if (AutoFitScript) + FitScript(); + } + private void OnCanvasPreviewMouseRightButtonDown(object sender, MouseButtonEventArgs args) { _lastRightClickLocation = args.GetPosition(_canvas); @@ -261,6 +302,12 @@ namespace Artemis.VisualScripting.Editor.Controls private void OnCanvasMouseRightButtonDown(object sender, MouseButtonEventArgs args) { + if (AutoFitScript) + { + args.Handled = true; + return; + } + _dragCanvas = true; _dragCanvasStartLocation = args.GetPosition(this); _dragCanvasStartOffset = _viewportCenter; @@ -289,7 +336,7 @@ namespace Artemis.VisualScripting.Editor.Controls { if (args.RightButton == MouseButtonState.Pressed) { - Vector newLocation = _dragCanvasStartOffset + (((args.GetPosition(this) - _dragCanvasStartLocation)) * (1.0 / Scale)); + Vector newLocation = _dragCanvasStartOffset - (((args.GetPosition(this) - _dragCanvasStartLocation)) * (1.0 / Scale)); CenterAt(newLocation); _movedDuringDrag = true; @@ -327,14 +374,18 @@ namespace Artemis.VisualScripting.Editor.Controls private void OnCanvasDragOver(object sender, DragEventArgs args) { - if (VisualScript == null) return; - if (VisualScript.IsConnecting) VisualScript.OnDragOver(args.GetPosition(_canvas)); } private void OnCanvasMouseWheel(object sender, MouseWheelEventArgs args) { + if (AutoFitScript) + { + args.Handled = true; + return; + } + if (args.Delta < 0) Scale /= ScaleFactor; else @@ -342,8 +393,6 @@ namespace Artemis.VisualScripting.Editor.Controls Scale = Clamp(Scale, MinScale, MaxScale); - _canvas.LayoutTransform = new ScaleTransform(Scale, Scale); - UpdatePanning(); args.Handled = true; @@ -373,9 +422,63 @@ namespace Artemis.VisualScripting.Editor.Controls if (d is not VisualScriptPresenter presenter) return; if (presenter.VisualScript == null) return; + presenter._canvasViewPortScale.ScaleX = presenter.Scale; + presenter._canvasViewPortScale.ScaleY = presenter.Scale; + presenter.VisualScript.NodeDragScale = 1.0 / presenter.Scale; } + private void FitScript() + { + if (_fitPending) return; + + _fitPending = true; + Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(FitScriptAction)); + } + + private void FitScriptAction() + { + _fitPending = false; + + if ((Script == null) || (_nodeList == null)) return; + + double minX = double.MaxValue; + double maxX = double.MinValue; + double minY = double.MaxValue; + double maxY = double.MinValue; + for (int i = 0; i < _nodeList.Items.Count; i++) + { + DependencyObject container = _nodeList.ItemContainerGenerator.ContainerFromIndex(i); + VisualScriptNodePresenter nodePresenter = GetChildOfType(container); + if (nodePresenter != null) + { + minX = Math.Min(minX, nodePresenter.Node.Node.X); + minY = Math.Min(minY, nodePresenter.Node.Node.Y); + + maxX = Math.Max(maxX, nodePresenter.Node.Node.X + nodePresenter.ActualWidth); + maxY = Math.Max(maxY, nodePresenter.Node.Node.Y + nodePresenter.ActualHeight); + } + } + + if (minX >= (double.MaxValue - 1)) + { + Scale = MaxScale; + CenterAt(new Vector(0, 0)); + } + else + { + double width = maxX - minX; + double height = maxY - minY; + + double scaleX = ActualWidth / width; + double scaleY = ActualHeight / height; + + Scale = Clamp(Math.Min(scaleX, scaleY) / 1.05, 0, MaxScale); //DarthAffe 21.08.2021: 5% Border + + CenterAt(new Vector(minX + (width / 2.0), minY + (height / 2.0))); + } + } + private void CreateNode(NodeData nodeData) { if (nodeData == null) return; @@ -385,21 +488,10 @@ namespace Artemis.VisualScripting.Editor.Controls INode node = nodeData.CreateNode(Script, null); node.Initialize(Script); + node.X = _lastRightClickLocation.X - VisualScript.LocationOffset; + node.Y = _lastRightClickLocation.Y - VisualScript.LocationOffset; + Script.AddNode(node); - - InitializeNode(node, _lastRightClickLocation); - } - - private void InitializeNode(INode node, Point? initialLocation = null) - { - VisualScriptNode visualScriptNode = new(VisualScript, node); - if (initialLocation != null) - { - visualScriptNode.X = initialLocation.Value.X; - visualScriptNode.Y = initialLocation.Value.Y; - } - visualScriptNode.SnapNodeToGrid(); - VisualScript.Nodes.Add(visualScriptNode); } private void CenterAt(Vector vector) @@ -414,8 +506,8 @@ namespace Artemis.VisualScripting.Editor.Controls if (_canvasViewPortTransform == null) return; double surfaceOffset = (SurfaceSize / 2.0) * Scale; - _canvasViewPortTransform.X = (((_viewportCenter.X * Scale) + (ActualWidth / 2.0))) - surfaceOffset; - _canvasViewPortTransform.Y = (((_viewportCenter.Y * Scale) + (ActualHeight / 2.0))) - surfaceOffset; + _canvasViewPortTransform.X = (((-_viewportCenter.X * Scale) + (ActualWidth / 2.0))) - surfaceOffset; + _canvasViewPortTransform.Y = (((-_viewportCenter.Y * Scale) + (ActualHeight / 2.0))) - surfaceOffset; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -436,6 +528,21 @@ namespace Artemis.VisualScripting.Editor.Controls return new Vector(x, y); } + public static T GetChildOfType(DependencyObject obj) + where T : DependencyObject + { + if (obj == null) return null; + + for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++) + { + DependencyObject child = VisualTreeHelper.GetChild(obj, i); + + T result = (child as T) ?? GetChildOfType(child); + if (result != null) return result; + } + return null; + } + #endregion } } diff --git a/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScript.cs b/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScript.cs index fa5ea0bd9..b63125a2b 100644 --- a/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScript.cs +++ b/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScript.cs @@ -2,8 +2,10 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; +using System.ComponentModel; using System.Linq; using System.Windows; +using System.Windows.Threading; using Artemis.Core; using Artemis.VisualScripting.Events; using Artemis.VisualScripting.ViewModel; @@ -14,6 +16,8 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper { #region Properties & Fields + private bool _cableRecreationPending = false; + private readonly HashSet _selectedNodes = new(); private readonly Dictionary _nodeStartPositions = new(); private double _nodeDragAccumulationX; @@ -31,6 +35,8 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper set => SetProperty(ref _nodeDragScale, value); } + internal double LocationOffset { get; } + private VisualScriptPin _isConnectingPin; private VisualScriptPin IsConnectingPin { @@ -42,6 +48,7 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper } } + private readonly Dictionary _nodeMapping = new(); public ObservableCollection Nodes { get; } = new(); public IEnumerable Cables => Nodes.SelectMany(n => n.InputPins.SelectMany(p => p.InternalConnections)) @@ -54,6 +61,13 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper #endregion + #region Events + + public event EventHandler NodeMoved; + public event EventHandler NodeCollectionChanged; + + #endregion + #region Constructors public VisualScript(INodeScript script, int surfaceSize, int gridSize) @@ -62,7 +76,15 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper this.SurfaceSize = surfaceSize; this.GridSize = gridSize; + LocationOffset = SurfaceSize / 2.0; + Nodes.CollectionChanged += OnNodeCollectionChanged; + + script.NodeAdded += OnScriptNodeAdded; + script.NodeRemoved += OnScriptRemovedAdded; + + foreach (INode node in Script.Nodes) + InitializeNode(node); } #endregion @@ -85,31 +107,69 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper RegisterNodes(args.NewItems.Cast()); } + private void OnScriptNodeAdded(object sender, INode node) + { + if (_nodeMapping.ContainsKey(node)) return; + + InitializeNode(node); + } + + private void OnScriptRemovedAdded(object sender, INode node) + { + if (!_nodeMapping.TryGetValue(node, out VisualScriptNode visualScriptNode)) return; + + Nodes.Remove(visualScriptNode); + } + + private void InitializeNode(INode node) + { + VisualScriptNode visualScriptNode = new(this, node); + visualScriptNode.SnapNodeToGrid(); + Nodes.Add(visualScriptNode); + } + private void RegisterNodes(IEnumerable nodes) { foreach (VisualScriptNode node in nodes) { + _nodeMapping.Add(node.Node, node); + node.IsSelectedChanged += OnNodeIsSelectedChanged; node.DragStarting += OnNodeDragStarting; node.DragEnding += OnNodeDragEnding; node.DragMoving += OnNodeDragMoving; + node.PropertyChanged += OnNodePropertyChanged; if (node.IsSelected) _selectedNodes.Add(node); } + + NodeCollectionChanged?.Invoke(this, EventArgs.Empty); } private void UnregisterNodes(IEnumerable nodes) { foreach (VisualScriptNode node in nodes) { + _nodeMapping.Remove(node.Node); + node.IsSelectedChanged -= OnNodeIsSelectedChanged; node.DragStarting -= OnNodeDragStarting; node.DragEnding -= OnNodeDragEnding; node.DragMoving -= OnNodeDragMoving; + node.PropertyChanged -= OnNodePropertyChanged; _selectedNodes.Remove(node); } + + NodeCollectionChanged?.Invoke(this, EventArgs.Empty); + } + + private void OnNodePropertyChanged(object sender, PropertyChangedEventArgs args) + { + if (string.Equals(args.PropertyName, nameof(VisualScriptNode.X), StringComparison.OrdinalIgnoreCase) + || string.Equals(args.PropertyName, nameof(VisualScriptNode.Y), StringComparison.OrdinalIgnoreCase)) + NodeMoved?.Invoke(this, EventArgs.Empty); } private void OnNodeIsSelectedChanged(object sender, VisualScriptNodeIsSelectedChangedEventArgs args) @@ -184,8 +244,18 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper Script.RemoveNode(node.Node); } + internal void RequestCableRecreation() + { + if (_cableRecreationPending) return; + + _cableRecreationPending = true; + Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(RecreateCables)); + } + internal void RecreateCables() { + _cableRecreationPending = false; + Dictionary pinMapping = Nodes.SelectMany(n => n.InputPins) .Concat(Nodes.SelectMany(n => n.OutputPins)) .Concat(Nodes.SelectMany(n => n.InputPinCollections.SelectMany(p => p.Pins))) @@ -200,13 +270,13 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper { foreach (IPin connectedPin in pin.ConnectedTo) if (!connectedPins.Contains(connectedPin)) - { - VisualScriptPin pin1 = pinMapping[pin]; - VisualScriptPin pin2 = pinMapping[connectedPin]; - VisualScriptCable cable = new(pin1, pin2, false); - pin1.InternalConnections.Add(cable); - pin2.InternalConnections.Add(cable); - } + if (pinMapping.TryGetValue(pin, out VisualScriptPin pin1) + && pinMapping.TryGetValue(connectedPin, out VisualScriptPin pin2)) + { + VisualScriptCable cable = new(pin1, pin2, false); + pin1.InternalConnections.Add(cable); + pin2.InternalConnections.Add(cable); + } connectedPins.Add(pin); } diff --git a/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptNode.cs b/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptNode.cs index b69f56a01..778bdc0b9 100644 --- a/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptNode.cs +++ b/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptNode.cs @@ -13,7 +13,7 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper { #region Properties & Fields - private double _locationOffset; + private readonly Dictionary _pinMapping = new(); public VisualScript Script { get; } public INode Node { get; } @@ -55,20 +55,20 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper public double X { - get => Node.X + _locationOffset; + get => Node.X + Script.LocationOffset; set { - Node.X = value - _locationOffset; + Node.X = value - Script.LocationOffset; OnPropertyChanged(); } } public double Y { - get => Node.Y + _locationOffset; + get => Node.Y + Script.LocationOffset; set { - Node.Y = value - _locationOffset; + Node.Y = value - Script.LocationOffset; OnPropertyChanged(); } } @@ -100,8 +100,6 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper Node.PropertyChanged += OnNodePropertyChanged; - _locationOffset = script.SurfaceSize / 2.0; - ValidatePins(); } @@ -114,6 +112,12 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper if (string.Equals(args.PropertyName, nameof(Node.Pins), StringComparison.OrdinalIgnoreCase) || string.Equals(args.PropertyName, nameof(Node.PinCollections), StringComparison.OrdinalIgnoreCase)) ValidatePins(); + + else if (string.Equals(args.PropertyName, nameof(Node.X), StringComparison.OrdinalIgnoreCase)) + OnPropertyChanged(nameof(X)); + + else if (string.Equals(args.PropertyName, nameof(Node.Y), StringComparison.OrdinalIgnoreCase)) + OnPropertyChanged(nameof(Y)); } private void ValidatePins() @@ -199,8 +203,30 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper } #endregion + + #region Pin Mapping + + _pinMapping.Clear(); + + foreach (VisualScriptPin pin in InputPins) + _pinMapping.Add(pin.Pin, pin); + + foreach (VisualScriptPin pin in OutputPins) + _pinMapping.Add(pin.Pin, pin); + + foreach (VisualScriptPinCollection pinCollection in InputPinCollections) + foreach (VisualScriptPin pin in pinCollection.Pins) + _pinMapping.Add(pin.Pin, pin); + + foreach (VisualScriptPinCollection pinCollection in OutputPinCollections) + foreach (VisualScriptPin pin in pinCollection.Pins) + _pinMapping.Add(pin.Pin, pin); + + #endregion } + internal VisualScriptPin GetVisualPin(IPin pin) => ((pin != null) && _pinMapping.TryGetValue(pin, out VisualScriptPin visualScriptPin)) ? visualScriptPin : null; + public void SnapNodeToGrid() { X -= X % Script.GridSize; @@ -219,8 +245,8 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper OnIsSelectedChanged(IsSelected, alterSelection); } - public void DragStart() => DragStarting?.Invoke(this, new EventArgs()); - public void DragEnd() => DragEnding?.Invoke(this, new EventArgs()); + public void DragStart() => DragStarting?.Invoke(this, EventArgs.Empty); + public void DragEnd() => DragEnding?.Invoke(this, EventArgs.Empty); public void DragMove(double dx, double dy) => DragMoving?.Invoke(this, new VisualScriptNodeDragMovingEventArgs(dx, dy)); private void OnIsSelectedChanged(bool isSelected, bool alterSelection) => IsSelectedChanged?.Invoke(this, new VisualScriptNodeIsSelectedChangedEventArgs(isSelected, alterSelection)); diff --git a/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptPin.cs b/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptPin.cs index 933d8125e..a3720cbf4 100644 --- a/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptPin.cs +++ b/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptPin.cs @@ -19,6 +19,8 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper #region Properties & Fields + private bool _isConnectionUpdated = false; + private VisualScriptPin _isConnectingPin; private VisualScriptCable _isConnectingCable; @@ -39,8 +41,7 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper } } - public Point AbsoluteCableTargetPosition => Pin.Direction == PinDirection.Input ? new Point(AbsolutePosition.X - CABLE_OFFSET, AbsolutePosition.Y) - : new Point(AbsolutePosition.X + CABLE_OFFSET, AbsolutePosition.Y); + public Point AbsoluteCableTargetPosition => Pin.Direction == PinDirection.Input ? new Point(AbsolutePosition.X - CABLE_OFFSET, AbsolutePosition.Y) : new Point(AbsolutePosition.X + CABLE_OFFSET, AbsolutePosition.Y); #endregion @@ -50,12 +51,22 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper { this.Node = node; this.Pin = pin; + + pin.PinConnected += PinConnectionChanged; + pin.PinDisconnected += PinConnectionChanged; } #endregion #region Methods + private void PinConnectionChanged(object sender, IPin pin) + { + if (_isConnectionUpdated) return; + + Node?.Script?.RequestCableRecreation(); + } + public void SetConnecting(bool isConnecting) { if (isConnecting) @@ -63,8 +74,7 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper if (_isConnectingCable != null) SetConnecting(false); - _isConnectingPin = new VisualScriptPin(null, new IsConnectingPin(Pin.Direction == PinDirection.Input ? PinDirection.Output : PinDirection.Input, Pin.Type)) - { AbsolutePosition = AbsolutePosition }; + _isConnectingPin = new VisualScriptPin(null, new IsConnectingPin(Pin.Direction == PinDirection.Input ? PinDirection.Output : PinDirection.Input, Pin.Type)) { AbsolutePosition = AbsolutePosition }; _isConnectingCable = new VisualScriptCable(this, _isConnectingPin); Node.OnIsConnectingPinChanged(_isConnectingPin); } @@ -81,6 +91,8 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper { if (InternalConnections.Contains(cable)) return; + _isConnectionUpdated = true; + if (Pin.Direction == PinDirection.Input) { List cables = InternalConnections.ToList(); @@ -92,21 +104,31 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper Pin.ConnectTo(cable.GetConnectedPin(Pin)); Node?.OnPinConnected(new PinConnectedEventArgs(this, cable)); + + _isConnectionUpdated = false; } public void DisconnectAll() { + _isConnectionUpdated = true; + List cables = InternalConnections.ToList(); foreach (VisualScriptCable cable in cables) cable.Disconnect(); + + _isConnectionUpdated = false; } internal void Disconnect(VisualScriptCable cable) { + _isConnectionUpdated = true; + InternalConnections.Remove(cable); Pin.DisconnectFrom(cable.GetConnectedPin(Pin)); Node?.OnPinDisconnected(new PinDisconnectedEventArgs(this, cable)); + + _isConnectionUpdated = false; } #endregion diff --git a/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptPinCollection.cs b/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptPinCollection.cs index 761542452..d9a1ac838 100644 --- a/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptPinCollection.cs +++ b/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptPinCollection.cs @@ -12,6 +12,7 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper public VisualScriptNode Node { get; } public IPinCollection PinCollection { get; } + private readonly Dictionary _pinMapping = new(); public ObservableCollection Pins { get; } = new(); #endregion @@ -33,27 +34,47 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper this.Node = node; this.PinCollection = pinCollection; + pinCollection.PinAdded += OnPinCollectionPinAdded; + pinCollection.PinRemoved += OnPinCollectionPinRemoved; + foreach (IPin pin in PinCollection) - Pins.Add(new VisualScriptPin(node, pin)); + { + VisualScriptPin visualScriptPin = new(node, pin); + _pinMapping.Add(pin, visualScriptPin); + Pins.Add(visualScriptPin); + } } #endregion #region Methods + private void OnPinCollectionPinRemoved(object sender, IPin pin) + { + if (!_pinMapping.TryGetValue(pin, out VisualScriptPin visualScriptPin)) return; + + visualScriptPin.DisconnectAll(); + Pins.Remove(visualScriptPin); + } + + private void OnPinCollectionPinAdded(object sender, IPin pin) + { + if (_pinMapping.ContainsKey(pin)) return; + + VisualScriptPin visualScriptPin = new(Node, pin); + _pinMapping.Add(pin, visualScriptPin); + Pins.Add(visualScriptPin); + } + public void AddPin() { - IPin pin = PinCollection.AddPin(); - Pins.Add(new VisualScriptPin(Node, pin)); + PinCollection.AddPin(); } public void RemovePin(VisualScriptPin pin) { - pin.DisconnectAll(); PinCollection.Remove(pin.Pin); - Pins.Remove(pin); } - public void RemoveAll() { List pins = new(Pins); From ed33d8a148cd53ab80c53616d985ada0f8175de2 Mon Sep 17 00:00:00 2001 From: Robert Date: Sun, 22 Aug 2021 00:20:28 +0200 Subject: [PATCH 20/47] Nodes - When usting custom VMs, create a VM for each presenter --- src/Artemis.Core/Services/NodeService.cs | 5 +-- src/Artemis.Core/VisualScripting/Node.cs | 21 ++++++++--- .../Controls/VisualScriptNodePresenter.cs | 36 +++++++++++++++---- .../Styles/VisualScriptNodePresenter.xaml | 12 +------ 4 files changed, 48 insertions(+), 26 deletions(-) diff --git a/src/Artemis.Core/Services/NodeService.cs b/src/Artemis.Core/Services/NodeService.cs index 82cf2d4f7..5af2a659a 100644 --- a/src/Artemis.Core/Services/NodeService.cs +++ b/src/Artemis.Core/Services/NodeService.cs @@ -83,10 +83,7 @@ namespace Artemis.Core.Services // ignored } } - - if (node is CustomViewModelNode customViewModelNode) - customViewModelNode.BaseCustomViewModel = _kernel.Get(customViewModelNode.CustomViewModelType, new ConstructorArgument("node", node)); - + node.Initialize(script); return node; } diff --git a/src/Artemis.Core/VisualScripting/Node.cs b/src/Artemis.Core/VisualScripting/Node.cs index ac6b4da8a..4d1acc1eb 100644 --- a/src/Artemis.Core/VisualScripting/Node.cs +++ b/src/Artemis.Core/VisualScripting/Node.cs @@ -1,6 +1,8 @@ -using System; +using Ninject; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using Ninject.Parameters; namespace Artemis.Core { @@ -149,14 +151,24 @@ namespace Artemis.Core public abstract class Node : CustomViewModelNode where T : ICustomNodeViewModel { + [Inject] + internal IKernel Kernel { get; set; } = null!; + protected Node() { } protected Node(string name, string description) : base(name, description) { } - public override Type CustomViewModelType => typeof(T); - public T CustomViewModel => (T) BaseCustomViewModel!; + public virtual T GetViewModel() + { + return Kernel.Get(new ConstructorArgument("node", this)); + } + + public override ICustomNodeViewModel GetCustomViewModel() + { + return GetViewModel(); + } } public abstract class CustomViewModelNode : Node @@ -169,7 +181,6 @@ namespace Artemis.Core protected CustomViewModelNode(string name, string description) : base(name, description) { } - public abstract Type CustomViewModelType { get; } - public object? BaseCustomViewModel { get; set; } + public abstract ICustomNodeViewModel GetCustomViewModel(); } } \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptNodePresenter.cs b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptNodePresenter.cs index e79a195de..cc88904df 100644 --- a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptNodePresenter.cs +++ b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptNodePresenter.cs @@ -3,6 +3,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; +using Artemis.Core; using Artemis.VisualScripting.Editor.Controls.Wrapper; namespace Artemis.VisualScripting.Editor.Controls @@ -21,20 +22,29 @@ namespace Artemis.VisualScripting.Editor.Controls #region Dependency Properties public static readonly DependencyProperty NodeProperty = DependencyProperty.Register( - "Node", typeof(VisualScriptNode), typeof(VisualScriptNodePresenter), new PropertyMetadata(default(VisualScriptNode))); + "Node", typeof(VisualScriptNode), typeof(VisualScriptNodePresenter), new PropertyMetadata(default(VisualScriptNode), NodePropertyChangedCallback)); public VisualScriptNode Node { - get => (VisualScriptNode)GetValue(NodeProperty); + get => (VisualScriptNode) GetValue(NodeProperty); set => SetValue(NodeProperty, value); } + public static readonly DependencyProperty CustomViewModelProperty = DependencyProperty.Register( + "CustomViewModel", typeof(ICustomNodeViewModel), typeof(VisualScriptNodePresenter), new PropertyMetadata(null)); + + public ICustomNodeViewModel CustomViewModel + { + get => (ICustomNodeViewModel) GetValue(CustomViewModelProperty); + set => SetValue(CustomViewModelProperty, value); + } + public static readonly DependencyProperty TitleBrushProperty = DependencyProperty.Register( "TitleBrush", typeof(Brush), typeof(VisualScriptNodePresenter), new PropertyMetadata(default(Brush))); public Brush TitleBrush { - get => (Brush)GetValue(TitleBrushProperty); + get => (Brush) GetValue(TitleBrushProperty); set => SetValue(TitleBrushProperty, value); } @@ -56,8 +66,8 @@ namespace Artemis.VisualScripting.Editor.Controls } Size neededSize = base.MeasureOverride(constraint); - int width = (int)Math.Ceiling(neededSize.Width); - int height = (int)Math.Ceiling(neededSize.Height); + int width = (int) Math.Ceiling(neededSize.Width); + int height = (int) Math.Ceiling(neededSize.Height); return new Size(SnapToGridSize(width), SnapToGridSize(height)); } @@ -132,6 +142,20 @@ namespace Artemis.VisualScripting.Editor.Controls args.Handled = true; } + private void GetCustomViewModel() + { + if (Node?.Node is CustomViewModelNode customViewModelNode) + CustomViewModel = customViewModelNode.GetCustomViewModel(); + else + CustomViewModel = null; + } + + private static void NodePropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is VisualScriptNodePresenter presenter) + presenter.GetCustomViewModel(); + } + #endregion } -} +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Editor/Styles/VisualScriptNodePresenter.xaml b/src/Artemis.VisualScripting/Editor/Styles/VisualScriptNodePresenter.xaml index c124dec3b..26d6ec27b 100644 --- a/src/Artemis.VisualScripting/Editor/Styles/VisualScriptNodePresenter.xaml +++ b/src/Artemis.VisualScripting/Editor/Styles/VisualScriptNodePresenter.xaml @@ -208,17 +208,7 @@ - - - - - - - - - - - + From 7933bf7ee999bbfba8b5a2a93c133384e29cfef5 Mon Sep 17 00:00:00 2001 From: Robert Date: Sun, 22 Aug 2021 00:56:23 +0200 Subject: [PATCH 21/47] Nodes - Added to float node Nodes - Updated almost all custom VMs to stay in sync across multiple instances --- .../VisualScripting/CustomNodeViewModel.cs | 20 +++- .../Controls/VisualScriptNodePresenter.cs | 20 ++++ .../Nodes/ConvertNodes.cs | 80 ++++++++++++++-- .../CustomViewModels/CustomNodeViewModel.cs | 19 ++-- .../DataModelNodeCustomViewModel.cs | 24 ++--- .../LayerPropertyNodeCustomViewModel.cs | 10 +- .../StaticValueNodeViewModels.cs | 95 ++++++++++++++----- .../Nodes/DataModelNode.cs | 14 ++- 8 files changed, 217 insertions(+), 65 deletions(-) diff --git a/src/Artemis.Core/VisualScripting/CustomNodeViewModel.cs b/src/Artemis.Core/VisualScripting/CustomNodeViewModel.cs index 5975ed806..7efc9956e 100644 --- a/src/Artemis.Core/VisualScripting/CustomNodeViewModel.cs +++ b/src/Artemis.Core/VisualScripting/CustomNodeViewModel.cs @@ -1,9 +1,23 @@ -using Newtonsoft.Json; - -namespace Artemis.Core +namespace Artemis.Core { + /// + /// Represents a custom view model for a + /// public interface ICustomNodeViewModel { + /// + /// Gets the node the view models belongs to + /// public INode Node { get; } + + /// + /// Called whenever the custom view model is activated + /// + void OnActivate(); + + /// + /// Called whenever the custom view model is closed + /// + void OnDeactivate(); } } \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptNodePresenter.cs b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptNodePresenter.cs index cc88904df..4532b9d8b 100644 --- a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptNodePresenter.cs +++ b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptNodePresenter.cs @@ -50,6 +50,15 @@ namespace Artemis.VisualScripting.Editor.Controls #endregion + #region Constructors + + public VisualScriptNodePresenter() + { + Unloaded += OnUnloaded; + } + + #endregion + #region Methods protected override Size MeasureOverride(Size constraint) @@ -144,8 +153,13 @@ namespace Artemis.VisualScripting.Editor.Controls private void GetCustomViewModel() { + CustomViewModel?.OnDeactivate(); + if (Node?.Node is CustomViewModelNode customViewModelNode) + { CustomViewModel = customViewModelNode.GetCustomViewModel(); + CustomViewModel.OnActivate(); + } else CustomViewModel = null; } @@ -156,6 +170,12 @@ namespace Artemis.VisualScripting.Editor.Controls presenter.GetCustomViewModel(); } + private void OnUnloaded(object sender, RoutedEventArgs e) + { + CustomViewModel?.OnDeactivate(); + CustomViewModel = null; + } + #endregion } } \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/ConvertNodes.cs b/src/Artemis.VisualScripting/Nodes/ConvertNodes.cs index f9d52f8dd..29215d8d1 100644 --- a/src/Artemis.VisualScripting/Nodes/ConvertNodes.cs +++ b/src/Artemis.VisualScripting/Nodes/ConvertNodes.cs @@ -60,10 +60,21 @@ namespace Artemis.VisualScripting.Nodes public override void Evaluate() { - if (!int.TryParse(Input.Value?.ToString(), out int value)) + Integer.Value = Input.Value switch + { + int input => input, + double input => (int) input, + float input => (int) input, + _ => TryParse(Input.Value) + }; + } + + private int TryParse(object input) + { + if (!int.TryParse(input?.ToString(), out int value)) value = 0; - Integer.Value = value; + return value; } #endregion @@ -95,12 +106,69 @@ namespace Artemis.VisualScripting.Nodes public override void Evaluate() { - if (!double.TryParse(Input.Value?.ToString(), out double value)) - value = 0; + Double.Value = Input.Value switch + { + int input => input, + double input => input, + float input => input, + _ => TryParse(Input.Value) + }; + } - Double.Value = value; + private double TryParse(object input) + { + if (!double.TryParse(input?.ToString(), out double value)) + value = 0.0; + + return value; } #endregion } -} + + [Node("To Float", "Converts the input to a float.")] + public class ConvertToFloatNode : Node + { + #region Properties & Fields + + public InputPin Input { get; } + + public OutputPin Float { get; } + + #endregion + + #region Constructors + + public ConvertToFloatNode() + : base("To Float", "Converts the input to a float.") + { + Input = CreateInputPin(); + Float = CreateOutputPin(); + } + + #endregion + + #region Methods + + public override void Evaluate() + { + Float.Value = Input.Value switch + { + int input => input, + double input => (float) input, + float input => input, + _ => TryParse(Input.Value) + }; + } + + private float TryParse(object input) + { + if (!float.TryParse(input?.ToString(), out float value)) + value = 0.0f; + + return value; + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/CustomViewModels/CustomNodeViewModel.cs b/src/Artemis.VisualScripting/Nodes/CustomViewModels/CustomNodeViewModel.cs index fca28dbbb..1c7d23809 100644 --- a/src/Artemis.VisualScripting/Nodes/CustomViewModels/CustomNodeViewModel.cs +++ b/src/Artemis.VisualScripting/Nodes/CustomViewModels/CustomNodeViewModel.cs @@ -1,10 +1,9 @@ -using System.Windows; -using Artemis.Core; +using Artemis.Core; using Stylet; namespace Artemis.VisualScripting.Nodes.CustomViewModels { - public abstract class CustomNodeViewModel : PropertyChangedBase, IViewAware, ICustomNodeViewModel + public abstract class CustomNodeViewModel : PropertyChangedBase, ICustomNodeViewModel { protected CustomNodeViewModel(INode node) { @@ -13,21 +12,17 @@ namespace Artemis.VisualScripting.Nodes.CustomViewModels public INode Node { get; } - #region Implementation of IViewAware + #region Implementation of ICustomNodeViewModel /// - public void AttachView(UIElement view) - { - View = view; - OnDisplay(); - } - - protected virtual void OnDisplay() + public virtual void OnActivate() { } /// - public UIElement View { get; private set; } + public virtual void OnDeactivate() + { + } #endregion } diff --git a/src/Artemis.VisualScripting/Nodes/CustomViewModels/DataModelNodeCustomViewModel.cs b/src/Artemis.VisualScripting/Nodes/CustomViewModels/DataModelNodeCustomViewModel.cs index 126a1815d..dccccb92a 100644 --- a/src/Artemis.VisualScripting/Nodes/CustomViewModels/DataModelNodeCustomViewModel.cs +++ b/src/Artemis.VisualScripting/Nodes/CustomViewModels/DataModelNodeCustomViewModel.cs @@ -1,4 +1,5 @@ -using Artemis.Core; +using System.ComponentModel; +using Artemis.Core; using Artemis.Core.Modules; using Stylet; @@ -26,7 +27,6 @@ namespace Artemis.VisualScripting.Nodes.CustomViewModels set { _node.DataModelPath = value; - OnPropertyChanged(nameof(DataModelPath)); if (_node.DataModelPath != null) { @@ -41,8 +41,8 @@ namespace Artemis.VisualScripting.Nodes.CustomViewModels _node.UpdateOutputPin(); } } - - public void Initialize() + + public override void OnActivate() { if (Modules != null) return; @@ -52,17 +52,19 @@ namespace Artemis.VisualScripting.Nodes.CustomViewModels Modules.Add(scriptProfile.Configuration.Module); else if (_node.Script.Context is ProfileConfiguration profileConfiguration && profileConfiguration.Module != null) Modules.Add(profileConfiguration.Module); + + _node.PropertyChanged += NodeOnPropertyChanged; } - #region Overrides of CustomNodeViewModel - - /// - protected override void OnDisplay() + public override void OnDeactivate() { - Initialize(); - base.OnDisplay(); + _node.PropertyChanged -= NodeOnPropertyChanged; } - #endregion + private void NodeOnPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(DataModelNode.DataModelPath)) + OnPropertyChanged(nameof(DataModelPath)); + } } } \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/CustomViewModels/LayerPropertyNodeCustomViewModel.cs b/src/Artemis.VisualScripting/Nodes/CustomViewModels/LayerPropertyNodeCustomViewModel.cs index edf84d7e1..05706be9a 100644 --- a/src/Artemis.VisualScripting/Nodes/CustomViewModels/LayerPropertyNodeCustomViewModel.cs +++ b/src/Artemis.VisualScripting/Nodes/CustomViewModels/LayerPropertyNodeCustomViewModel.cs @@ -66,15 +66,15 @@ namespace Artemis.VisualScripting.Nodes.CustomViewModels NotifyOfPropertyChange(nameof(SelectedLayerProperty)); } - #region Overrides of CustomNodeViewModel - - /// - protected override void OnDisplay() + public override void OnActivate() { GetProfileElements(); GetLayerProperties(); } - #endregion + public override void OnDeactivate() + { + base.OnDeactivate(); + } } } \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/CustomViewModels/StaticValueNodeViewModels.cs b/src/Artemis.VisualScripting/Nodes/CustomViewModels/StaticValueNodeViewModels.cs index 17b09f66e..82087eb87 100644 --- a/src/Artemis.VisualScripting/Nodes/CustomViewModels/StaticValueNodeViewModels.cs +++ b/src/Artemis.VisualScripting/Nodes/CustomViewModels/StaticValueNodeViewModels.cs @@ -1,4 +1,4 @@ -using Artemis.Core; +using System.ComponentModel; namespace Artemis.VisualScripting.Nodes.CustomViewModels { @@ -13,12 +13,24 @@ namespace Artemis.VisualScripting.Nodes.CustomViewModels public double Input { - get => (double) _node.Storage; - set - { - _node.Storage = value; + get => (double) (_node.Storage ?? 0.0); + 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)); - } } } @@ -33,12 +45,24 @@ namespace Artemis.VisualScripting.Nodes.CustomViewModels public float Input { - get => (float)_node.Storage; - set - { - _node.Storage = value; + get => (float) (_node.Storage ?? 0f); + 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)); - } } } @@ -53,17 +77,24 @@ namespace Artemis.VisualScripting.Nodes.CustomViewModels public int Input { - get - { - if (_node.Storage is long longInput) - return (int)longInput; - return (int)_node.Storage; - } - set - { - _node.Storage = value; + get => _node.Storage is long longInput ? (int) longInput : (int) (_node.Storage ?? 0); + 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)); - } } } @@ -78,12 +109,24 @@ namespace Artemis.VisualScripting.Nodes.CustomViewModels public string Input { - get => (string)_node.Storage; - set - { - _node.Storage = value; + 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/DataModelNode.cs b/src/Artemis.VisualScripting/Nodes/DataModelNode.cs index 64b28a169..4f7a825c4 100644 --- a/src/Artemis.VisualScripting/Nodes/DataModelNode.cs +++ b/src/Artemis.VisualScripting/Nodes/DataModelNode.cs @@ -9,13 +9,20 @@ namespace Artemis.VisualScripting.Nodes [Node("Data Model-Value", "Outputs a selectable data model value.")] public class DataModelNode : Node { + private DataModelPath _dataModelPath; + public DataModelNode() : base("Data Model", "Outputs a selectable data model value") { } public INodeScript Script { get; private set; } public OutputPin Output { get; private set; } - public DataModelPath DataModelPath { get; set; } + + public DataModelPath DataModelPath + { + get => _dataModelPath; + set => SetAndNotify(ref _dataModelPath , value); + } public override void Initialize(INodeScript script) { @@ -36,7 +43,10 @@ namespace Artemis.VisualScripting.Nodes public void UpdateOutputPin() { - if (Pins.Contains(Output)) + if (Output != null && Output.Type == DataModelPath?.GetPropertyType()) + return; + + if (Output != null && Pins.Contains(Output)) { RemovePin(Output); Output = null; From 0bb93f2ebd242b7935ed8d2027fd8b54c6548975 Mon Sep 17 00:00:00 2001 From: Robert Date: Sun, 22 Aug 2021 18:17:05 +0200 Subject: [PATCH 22/47] Data bindings - Rewrote data binding system to fully leverage nodes --- src/Artemis.Core/Artemis.Core.csproj | 4 + .../Converters/FloatDataBindingConverter.cs | 47 --- .../Converters/GeneralDataBindingConverter.cs | 33 -- .../Converters/IntDataBindingConverter.cs | 41 -- .../Converters/SKColorDataBindingConverter.cs | 29 -- .../Properties/BoolLayerProperty.cs | 7 +- .../Properties/ColorGradientLayerProperty.cs | 28 +- .../Properties/EnumLayerProperty.cs | 1 - .../Properties/FloatLayerProperty.cs | 7 +- .../Properties/FloatRangeLayerProperty.cs | 10 +- .../Properties/IntLayerProperty.cs | 7 +- .../Properties/IntRangeLayerProperty.cs | 10 +- .../LayerBrushReferenceLayerProperty.cs | 1 - .../Properties/SKColorLayerProperty.cs | 7 +- .../Properties/SKPointLayerProperty.cs | 9 +- .../Properties/SKSizeLayerProperty.cs | 9 +- .../Events/Profiles/DataBindingEventArgs.cs | 20 + .../Profile/DataBindings/DataBinding.cs | 261 ++++++------- .../DataBindings/DataBindingConverter.cs | 90 ----- .../DataBindings/DataBindingProperty.cs | 46 +++ .../DataBindings/DataBindingRegistration.cs | 87 ----- .../Profile/DataBindings/IDataBinding.cs | 42 ++- .../DataBindings/IDataBindingConverter.cs | 16 - ...egistration.cs => IDataBindingProperty.cs} | 26 +- .../Profile/LayerProperties/ILayerProperty.cs | 47 ++- .../Profile/LayerProperties/LayerProperty.cs | 352 +++++------------- .../Models/Profile/RenderProfileElement.cs | 7 +- .../VisualScripting/DataBindingNodeScript.cs | 36 ++ src/Artemis.Core/VisualScripting/InputPin.cs | 2 +- .../Internal/DataBindingExitNode.cs | 73 ++++ .../Profile/DataBindings/DataBindingEntity.cs | 6 +- .../Entities/Profile/PropertyEntity.cs | 3 +- .../PropertyInput/PropertyInputViewModel.cs | 16 +- .../Interfaces/IProfileEditorService.cs | 14 +- .../Services/ProfileEditorService.cs | 8 +- .../BoolPropertyInputViewModel.cs | 8 +- .../PropertyInput/BrushPropertyInputView.xaml | 1 - .../BrushPropertyInputViewModel.cs | 4 +- .../FloatPropertyInputViewModel.cs | 10 - .../FloatRangePropertyInputView.xaml | 2 +- .../FloatRangePropertyInputViewModel.cs | 13 - .../IntPropertyInputViewModel.cs | 10 - .../IntRangePropertyInputView.xaml | 4 +- .../IntRangePropertyInputViewModel.cs | 13 - .../SKColorPropertyInputView.xaml | 4 +- .../SKColorPropertyInputViewModel.cs | 10 - .../SKPointPropertyInputView.xaml | 4 +- .../SKPointPropertyInputViewModel.cs | 13 - .../SKSizePropertyInputView.xaml | 4 +- .../SKSizePropertyInputViewModel.cs | 13 - .../Ninject/Factories/IVMFactory.cs | 5 - .../DataBindingsViewModelInstanceProvider.cs | 26 -- src/Artemis.UI/Ninject/UiModule.cs | 1 - .../DataBindings/DataBindingView.xaml | 167 --------- .../DataBindings/DataBindingView.xaml.cs | 15 - .../DataBindings/DataBindingViewModel.cs | 251 ------------- .../DataBindings/DataBindingsView.xaml | 58 ++- .../DataBindings/DataBindingsViewModel.cs | 83 ++--- .../LayerProperties/LayerPropertiesView.xaml | 1 - .../Tree/TreePropertyViewModel.cs | 16 +- .../Visualization/ProfileViewModel.cs | 39 +- .../LayerPropertyNodeCustomViewModel.cs | 2 +- .../Nodes/LayerPropertyNode.cs | 8 +- 63 files changed, 654 insertions(+), 1533 deletions(-) delete mode 100644 src/Artemis.Core/DefaultTypes/DataBindings/Converters/FloatDataBindingConverter.cs delete mode 100644 src/Artemis.Core/DefaultTypes/DataBindings/Converters/GeneralDataBindingConverter.cs delete mode 100644 src/Artemis.Core/DefaultTypes/DataBindings/Converters/IntDataBindingConverter.cs delete mode 100644 src/Artemis.Core/DefaultTypes/DataBindings/Converters/SKColorDataBindingConverter.cs create mode 100644 src/Artemis.Core/Events/Profiles/DataBindingEventArgs.cs delete mode 100644 src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs create mode 100644 src/Artemis.Core/Models/Profile/DataBindings/DataBindingProperty.cs delete mode 100644 src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs delete mode 100644 src/Artemis.Core/Models/Profile/DataBindings/IDataBindingConverter.cs rename src/Artemis.Core/Models/Profile/DataBindings/{IDataBindingRegistration.cs => IDataBindingProperty.cs} (55%) create mode 100644 src/Artemis.Core/VisualScripting/DataBindingNodeScript.cs create mode 100644 src/Artemis.Core/VisualScripting/Internal/DataBindingExitNode.cs delete mode 100644 src/Artemis.UI/Ninject/InstanceProviders/DataBindingsViewModelInstanceProvider.cs delete mode 100644 src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml delete mode 100644 src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml.cs delete mode 100644 src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs diff --git a/src/Artemis.Core/Artemis.Core.csproj b/src/Artemis.Core/Artemis.Core.csproj index 6e416d8dd..4cb259807 100644 --- a/src/Artemis.Core/Artemis.Core.csproj +++ b/src/Artemis.Core/Artemis.Core.csproj @@ -84,4 +84,8 @@ PreserveNewest + + + + \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Converters/FloatDataBindingConverter.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Converters/FloatDataBindingConverter.cs deleted file mode 100644 index 75818ac95..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Converters/FloatDataBindingConverter.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; - -namespace Artemis.Core -{ - /// - public class FloatDataBindingConverter : FloatDataBindingConverter - { - } - - /// - /// The type of layer property this converter is applied to - public class FloatDataBindingConverter : DataBindingConverter - { - /// - /// Creates a new instance of the class - /// - public FloatDataBindingConverter() - { - SupportsSum = true; - SupportsInterpolate = true; - } - - /// - public override float Sum(float a, float b) - { - return a + b; - } - - /// - public override float Interpolate(float a, float b, double progress) - { - float diff = b - a; - return (float) (a + diff * progress); - } - - /// - public override void ApplyValue(float value) - { - if (DataBinding!.LayerProperty.PropertyDescription.MaxInputValue is float max) - value = Math.Min(value, max); - if (DataBinding!.LayerProperty.PropertyDescription.MinInputValue is float min) - value = Math.Max(value, min); - - base.ApplyValue(value); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Converters/GeneralDataBindingConverter.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Converters/GeneralDataBindingConverter.cs deleted file mode 100644 index 584967a76..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Converters/GeneralDataBindingConverter.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; - -namespace Artemis.Core -{ - /// - /// Represents a generic data binding converter that acts as the bridge between a - /// and a and does not support - /// sum or interpolation - /// - public class GeneralDataBindingConverter : DataBindingConverter - { - /// - /// Creates a new instance of the class - /// - public GeneralDataBindingConverter() - { - SupportsSum = false; - SupportsInterpolate = false; - } - - /// - public override T Sum(T a, T b) - { - throw new NotSupportedException(); - } - - /// - public override T Interpolate(T a, T b, double progress) - { - throw new NotSupportedException(); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Converters/IntDataBindingConverter.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Converters/IntDataBindingConverter.cs deleted file mode 100644 index 795145630..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Converters/IntDataBindingConverter.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; - -namespace Artemis.Core -{ - /// - public class IntDataBindingConverter : IntDataBindingConverter - { - } - - /// - public class IntDataBindingConverter : DataBindingConverter - { - /// - /// Creates a new instance of the class - /// - public IntDataBindingConverter() - { - SupportsSum = true; - SupportsInterpolate = true; - } - - /// - /// Gets or sets the mode used for rounding during interpolation. Defaults to - /// - /// - public MidpointRounding InterpolationRoundingMode { get; set; } = MidpointRounding.AwayFromZero; - - /// - public override int Sum(int a, int b) - { - return a + b; - } - - /// - public override int Interpolate(int a, int b, double progress) - { - int diff = b - a; - return (int) Math.Round(a + diff * progress, InterpolationRoundingMode); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Converters/SKColorDataBindingConverter.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Converters/SKColorDataBindingConverter.cs deleted file mode 100644 index 6b708e088..000000000 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Converters/SKColorDataBindingConverter.cs +++ /dev/null @@ -1,29 +0,0 @@ -using SkiaSharp; - -namespace Artemis.Core -{ - /// - public class SKColorDataBindingConverter : DataBindingConverter - { - /// - /// Creates a new instance of the class - /// - public SKColorDataBindingConverter() - { - SupportsInterpolate = true; - SupportsSum = true; - } - - /// - public override SKColor Sum(SKColor a, SKColor b) - { - return a.Sum(b); - } - - /// - public override SKColor Interpolate(SKColor a, SKColor b, double progress) - { - return a.Interpolate(b, (float) progress); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Properties/BoolLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/BoolLayerProperty.cs index 99a7b614a..ccaecf8b8 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/BoolLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/BoolLayerProperty.cs @@ -4,9 +4,14 @@ public class BoolLayerProperty : LayerProperty { internal BoolLayerProperty() + { + } + + /// + protected override void OnInitialize() { KeyframesSupported = false; - RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new GeneralDataBindingConverter(), "Value"); + DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs index e811a4e0f..75d7c8a61 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs @@ -12,7 +12,6 @@ namespace Artemis.Core internal ColorGradientLayerProperty() { KeyframesSupported = false; - DataBindingsSupported = true; DefaultValue = new ColorGradient(); CurrentValueSet += OnCurrentValueSet; @@ -20,7 +19,7 @@ namespace Artemis.Core private void CreateDataBindingRegistrations() { - ClearDataBindingProperties(); + DataBinding.ClearDataBindingProperties(); if (CurrentValue == null) return; @@ -33,7 +32,7 @@ namespace Artemis.Core CurrentValue[stopIndex].Color = value; } - RegisterDataBindingProperty(() => CurrentValue[stopIndex].Color, Setter, new ColorStopDataBindingConverter(), $"Color #{stopIndex + 1}"); + DataBinding.RegisterDataBindingProperty(() => CurrentValue[stopIndex].Color, Setter, $"Color #{stopIndex + 1}"); } } @@ -71,7 +70,7 @@ namespace Artemis.Core private void SubscribedGradientOnPropertyChanged(object? sender, NotifyCollectionChangedEventArgs args) { - if (CurrentValue.Count != GetAllDataBindingRegistrations().Count) + if (CurrentValue.Count != DataBinding.Properties.Count) CreateDataBindingRegistrations(); } @@ -89,25 +88,4 @@ namespace Artemis.Core #endregion } - - internal class ColorStopDataBindingConverter : DataBindingConverter - { - public ColorStopDataBindingConverter() - { - SupportsInterpolate = true; - SupportsSum = true; - } - - /// - public override SKColor Sum(SKColor a, SKColor b) - { - return a.Sum(b); - } - - /// - public override SKColor Interpolate(SKColor a, SKColor b, double progress) - { - return a.Interpolate(b, (float) progress); - } - } } \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Properties/EnumLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/EnumLayerProperty.cs index eb99a3b3e..ea7962b7a 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/EnumLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/EnumLayerProperty.cs @@ -8,7 +8,6 @@ namespace Artemis.Core internal EnumLayerProperty() { KeyframesSupported = false; - DataBindingsSupported = false; } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/FloatLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/FloatLayerProperty.cs index cb1a6eae8..2aec47126 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/FloatLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/FloatLayerProperty.cs @@ -5,7 +5,12 @@ { internal FloatLayerProperty() { - RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new FloatDataBindingConverter(), "Value"); + } + + /// + protected override void OnInitialize() + { + DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs index 34a3be646..d43dbf705 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs @@ -5,13 +5,17 @@ { internal FloatRangeLayerProperty() { - RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue.Start = value, new FloatDataBindingConverter(), "Start"); - RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, new FloatDataBindingConverter(), "End"); - CurrentValueSet += OnCurrentValueSet; DefaultValue = new FloatRange(); } + /// + protected override void OnInitialize() + { + DataBinding.RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue.Start = value, "Start"); + DataBinding.RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, "End"); + } + /// protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased) { diff --git a/src/Artemis.Core/DefaultTypes/Properties/IntLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/IntLayerProperty.cs index f69a1bdbc..03681df72 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/IntLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/IntLayerProperty.cs @@ -7,7 +7,12 @@ namespace Artemis.Core { internal IntLayerProperty() { - RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new IntDataBindingConverter(), "Value"); + } + + /// + protected override void OnInitialize() + { + DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs index 0b1942356..fea761b8e 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs @@ -5,13 +5,17 @@ { internal IntRangeLayerProperty() { - RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue.Start = value, new IntDataBindingConverter(), "Start"); - RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, new IntDataBindingConverter(), "End"); - CurrentValueSet += OnCurrentValueSet; DefaultValue = new IntRange(); } + /// + protected override void OnInitialize() + { + DataBinding.RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue.Start = value, "Start"); + DataBinding.RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, "End"); + } + /// protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased) { diff --git a/src/Artemis.Core/DefaultTypes/Properties/LayerBrushReferenceLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/LayerBrushReferenceLayerProperty.cs index 9bbd77eac..c3341ee32 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/LayerBrushReferenceLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/LayerBrushReferenceLayerProperty.cs @@ -8,7 +8,6 @@ internal LayerBrushReferenceLayerProperty() { KeyframesSupported = false; - DataBindingsSupported = false; } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/SKColorLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/SKColorLayerProperty.cs index 46370568a..38caad133 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/SKColorLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/SKColorLayerProperty.cs @@ -7,7 +7,12 @@ namespace Artemis.Core { internal SKColorLayerProperty() { - RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new SKColorDataBindingConverter(), "Value"); + } + + /// + protected override void OnInitialize() + { + DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs index 3cd654ad9..df9517eeb 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs @@ -7,8 +7,13 @@ namespace Artemis.Core { internal SKPointLayerProperty() { - RegisterDataBindingProperty(() => CurrentValue.X, value => CurrentValue = new SKPoint(value, CurrentValue.Y), new FloatDataBindingConverter(), "X"); - RegisterDataBindingProperty(() => CurrentValue.Y, value => CurrentValue = new SKPoint(CurrentValue.X, value), new FloatDataBindingConverter(), "Y"); + } + + /// + protected override void OnInitialize() + { + DataBinding.RegisterDataBindingProperty(() => CurrentValue.X, value => CurrentValue = new SKPoint(value, CurrentValue.Y), "X"); + DataBinding.RegisterDataBindingProperty(() => CurrentValue.Y, value => CurrentValue = new SKPoint(CurrentValue.X, value), "Y"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/SKSizeLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/SKSizeLayerProperty.cs index 3274c26f9..e8344e0c9 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/SKSizeLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/SKSizeLayerProperty.cs @@ -7,8 +7,13 @@ namespace Artemis.Core { internal SKSizeLayerProperty() { - RegisterDataBindingProperty(() => CurrentValue.Width, (value) => CurrentValue = new SKSize(value, CurrentValue.Height), new FloatDataBindingConverter(), "Width"); - RegisterDataBindingProperty(() => CurrentValue.Height, (value) => CurrentValue = new SKSize(CurrentValue.Width, value), new FloatDataBindingConverter(), "Height"); + } + + /// + protected override void OnInitialize() + { + DataBinding.RegisterDataBindingProperty(() => CurrentValue.Width, (value) => CurrentValue = new SKSize(value, CurrentValue.Height), "Width"); + DataBinding.RegisterDataBindingProperty(() => CurrentValue.Height, (value) => CurrentValue = new SKSize(CurrentValue.Width, value), "Height"); } /// diff --git a/src/Artemis.Core/Events/Profiles/DataBindingEventArgs.cs b/src/Artemis.Core/Events/Profiles/DataBindingEventArgs.cs new file mode 100644 index 000000000..c01cfae65 --- /dev/null +++ b/src/Artemis.Core/Events/Profiles/DataBindingEventArgs.cs @@ -0,0 +1,20 @@ +using System; + +namespace Artemis.Core +{ + /// + /// Provides data for data binding events. + /// + public class DataBindingEventArgs : EventArgs + { + internal DataBindingEventArgs(IDataBinding dataBinding) + { + DataBinding = dataBinding; + } + + /// + /// Gets the data binding this event is related to + /// + public IDataBinding DataBinding { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs index e2392c79e..e02908256 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs @@ -1,25 +1,24 @@ using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; using Artemis.Storage.Entities.Profile.DataBindings; namespace Artemis.Core { /// - public class DataBinding : IDataBinding + public class DataBinding : IDataBinding { - private TProperty _currentValue = default!; + private readonly List _properties = new(); private bool _disposed; - private TimeSpan _easingProgress; - private TProperty _lastAppliedValue = default!; - private TProperty _previousValue = default!; - private bool _reapplyValue; + private bool _isEnabled; - internal DataBinding(DataBindingRegistration dataBindingRegistration) + internal DataBinding(LayerProperty layerProperty) { - LayerProperty = dataBindingRegistration.LayerProperty; - Entity = new DataBindingEntity(); + LayerProperty = layerProperty; - ApplyRegistration(dataBindingRegistration); - Script = new NodeScript(GetScriptName(), "The value to put into the data binding", LayerProperty.ProfileElement.Profile); + Entity = new DataBindingEntity(); + Script = new DataBindingNodeScript(GetScriptName(), "The value to put into the data binding", this, LayerProperty.ProfileElement.Profile); Save(); } @@ -27,39 +26,23 @@ namespace Artemis.Core internal DataBinding(LayerProperty layerProperty, DataBindingEntity entity) { LayerProperty = layerProperty; + Entity = entity; - Script = new NodeScript(GetScriptName(), "The value to put into the data binding", LayerProperty.ProfileElement.Profile); + Script = new DataBindingNodeScript(GetScriptName(), "The value to put into the data binding", this, LayerProperty.ProfileElement.Profile); // Load will add children so be initialized before that Load(); } - /// - /// Gets the data binding registration this data binding is based upon - /// - public DataBindingRegistration? Registration { get; private set; } - /// /// Gets the layer property this data binding targets /// public LayerProperty LayerProperty { get; } /// - /// Gets the converter used to apply this data binding to the + /// Gets the script used to populate the data binding /// - public DataBindingConverter? Converter { get; private set; } - - public NodeScript Script { get; private set; } - - /// - /// Gets or sets the easing time of the data binding - /// - public TimeSpan EasingTime { get; set; } - - /// - /// Gets ors ets the easing function of the data binding - /// - public Easings.Functions EasingFunction { get; set; } + public DataBindingNodeScript Script { get; private set; } /// /// Gets the data binding entity this data binding uses for persistent storage @@ -67,38 +50,52 @@ namespace Artemis.Core public DataBindingEntity Entity { get; } /// - /// Gets the current value of the data binding + /// Updates the pending values of this data binding /// - /// The base value of the property the data binding is applied to - /// - public TProperty GetValue(TProperty baseValue) + public void Update() { if (_disposed) throw new ObjectDisposedException("DataBinding"); - if (Converter == null) - return baseValue; + if (!IsEnabled) + return; + + // TODO: Update the base node Script.Run(); - - // If no easing is to be applied simple return whatever the current value is - if (EasingTime == TimeSpan.Zero || !Converter.SupportsInterpolate) - return Script.Result; - - // If the value changed, update the current and previous values used for easing - if (!Equals(Script.Result, _currentValue)) - ResetEasing(Script.Result); - - // Apply interpolation between the previous and current value - return GetInterpolatedValue(); } /// - /// Returns the type of the target property of this data binding + /// Registers a data binding property so that is available to the data binding system /// - public Type? GetTargetType() + /// The type of the layer property + /// The function to call to get the value of the property + /// The action to call to set the value of the property + /// The display name of the data binding property + public DataBindingProperty RegisterDataBindingProperty(Func getter, Action setter, string displayName) { - return Registration?.Getter.Method.ReturnType; + if (_disposed) + throw new ObjectDisposedException("DataBinding"); + if (Properties.Any(d => d.DisplayName == displayName)) + throw new ArtemisCoreException($"A databinding property named '{displayName}' is already registered."); + + DataBindingProperty property = new(getter, setter, displayName); + _properties.Add(property); + + OnDataBindingPropertyRegistered(); + return property; + } + + /// + /// Removes all data binding properties so they are no longer available to the data binding system + /// + public void ClearDataBindingProperties() + { + if (_disposed) + throw new ObjectDisposedException("LayerProperty"); + + _properties.Clear(); + OnDataBindingPropertiesCleared(); } /// @@ -113,111 +110,78 @@ namespace Artemis.Core if (disposing) { _disposed = true; - - if (Registration != null) - Registration.DataBinding = null; - Script.Dispose(); } } - private void ResetEasing(TProperty value) + /// + /// Invokes the event + /// + protected virtual void OnDataBindingPropertyRegistered() { - _previousValue = GetInterpolatedValue(); - _currentValue = value; - _easingProgress = TimeSpan.Zero; - } - - private void ApplyRegistration(DataBindingRegistration dataBindingRegistration) - { - if (dataBindingRegistration == null) - throw new ArgumentNullException(nameof(dataBindingRegistration)); - - dataBindingRegistration.DataBinding = this; - Converter = dataBindingRegistration.Converter; - Registration = dataBindingRegistration; - - if (GetTargetType()!.IsValueType) - { - if (_currentValue == null) - _currentValue = default!; - if (_previousValue == null) - _previousValue = default!; - } - - Converter?.Initialize(this); - } - - private TProperty GetInterpolatedValue() - { - if (_easingProgress == EasingTime || Converter == null || !Converter.SupportsInterpolate) - return _currentValue; - - double easingAmount = _easingProgress.TotalSeconds / EasingTime.TotalSeconds; - return Converter.Interpolate(_previousValue, _currentValue, Easings.Interpolate(easingAmount, EasingFunction)); + DataBindingPropertyRegistered?.Invoke(this, new DataBindingEventArgs(this)); } /// - /// Updates the smoothing progress of the data binding + /// Invokes the event /// - /// The timeline to apply during update - public void Update(Timeline timeline) + protected virtual void OnDataBindingPropertiesCleared() { - // Don't update data bindings if there is no delta, otherwise this creates an inconsistency between - // data bindings with easing and data bindings without easing (the ones with easing will seemingly not update) - if (timeline.Delta == TimeSpan.Zero || timeline.IsOverridden) - return; + DataBindingPropertiesCleared?.Invoke(this, new DataBindingEventArgs(this)); + } - UpdateWithDelta(timeline.Delta); + /// + /// Invokes the event + /// + protected virtual void OnDataBindingEnabled(DataBindingEventArgs e) + { + DataBindingEnabled?.Invoke(this, e); + } + + /// + /// Invokes the event + /// + protected virtual void OnDataBindingDisabled(DataBindingEventArgs e) + { + DataBindingDisabled?.Invoke(this, e); + } + + private string GetScriptName() + { + return LayerProperty.PropertyDescription.Name ?? LayerProperty.Path; } /// - public void UpdateWithDelta(TimeSpan delta) + public ILayerProperty BaseLayerProperty => LayerProperty; + + /// + public bool IsEnabled { - if (_disposed) - throw new ObjectDisposedException("DataBinding"); + get => _isEnabled; + set + { + _isEnabled = value; - // Data bindings cannot go back in time like brushes - if (delta < TimeSpan.Zero) - delta = TimeSpan.Zero; - - _easingProgress = _easingProgress.Add(delta); - if (_easingProgress > EasingTime) - _easingProgress = EasingTime; - - // Tell Apply() to apply a new value next call - _reapplyValue = false; + if (_isEnabled) + OnDataBindingEnabled(new DataBindingEventArgs(this)); + else + OnDataBindingDisabled(new DataBindingEventArgs(this)); + } } + /// + public ReadOnlyCollection Properties => _properties.AsReadOnly(); + /// public void Apply() { if (_disposed) throw new ObjectDisposedException("DataBinding"); - if (Converter == null) + if (!IsEnabled) return; - // If Update() has not been called, reapply the previous value - if (_reapplyValue) - { - Converter.ApplyValue(_lastAppliedValue); - return; - } - - TProperty converterValue = Converter.GetValue(); - TProperty value = GetValue(converterValue); - Converter.ApplyValue(value); - - _lastAppliedValue = value; - _reapplyValue = true; - } - - private string GetScriptName() - { - if (LayerProperty.GetAllDataBindingRegistrations().Count == 1) - return LayerProperty.PropertyDescription.Name ?? LayerProperty.Path; - return $"{LayerProperty.PropertyDescription.Name ?? LayerProperty.Path} - {Registration?.DisplayName}"; + Script.DataBindingExitNode.ApplyToDataBinding(); } /// @@ -227,6 +191,19 @@ namespace Artemis.Core GC.SuppressFinalize(this); } + /// + public event EventHandler? DataBindingPropertyRegistered; + + /// + public event EventHandler? DataBindingPropertiesCleared; + + /// + public event EventHandler? DataBindingEnabled; + + /// + public event EventHandler? DataBindingDisabled; + + #region Storage /// @@ -234,14 +211,6 @@ namespace Artemis.Core { if (_disposed) throw new ObjectDisposedException("DataBinding"); - - // General - DataBindingRegistration? registration = LayerProperty.GetDataBindingRegistration(Entity.Identifier); - if (registration != null) - ApplyRegistration(registration); - - EasingTime = Entity.EasingTime; - EasingFunction = (Easings.Functions) Entity.EasingFunction; } /// @@ -249,8 +218,8 @@ namespace Artemis.Core { Script.Dispose(); Script = Entity.NodeScript != null - ? new NodeScript(GetScriptName(), "The value to put into the data binding", Entity.NodeScript, LayerProperty.ProfileElement.Profile) - : new NodeScript(GetScriptName(), "The value to put into the data binding", LayerProperty.ProfileElement.Profile); + ? new DataBindingNodeScript(GetScriptName(), "The value to put into the data binding", this, Entity.NodeScript, LayerProperty.ProfileElement.Profile) + : new DataBindingNodeScript(GetScriptName(), "The value to put into the data binding", this, LayerProperty.ProfileElement.Profile); } /// @@ -259,17 +228,7 @@ namespace Artemis.Core if (_disposed) throw new ObjectDisposedException("DataBinding"); - if (!LayerProperty.Entity.DataBindingEntities.Contains(Entity)) - LayerProperty.Entity.DataBindingEntities.Add(Entity); - - // Don't save an invalid state - if (Registration != null) - Entity.Identifier = Registration.DisplayName; - - Entity.EasingTime = EasingTime; - Entity.EasingFunction = (int) EasingFunction; - - Script?.Save(); + Script.Save(); Entity.NodeScript = Script?.Entity; } diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs deleted file mode 100644 index 2b3d7eb5a..000000000 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; - -namespace Artemis.Core -{ - /// - /// Represents a data binding converter that acts as the bridge between a - /// and a - /// - public abstract class DataBindingConverter : IDataBindingConverter - { - /// - /// Gets the data binding this converter is applied to - /// - public DataBinding? DataBinding { get; private set; } - - /// - /// Gets whether or not this data binding converter supports the method - /// - public bool SupportsSum { get; protected set; } - - /// - /// Gets whether or not this data binding converter supports the method - /// - public bool SupportsInterpolate { get; protected set; } - - /// - /// Returns the sum of and - /// - public abstract TProperty Sum(TProperty a, TProperty b); - - /// - /// Returns the the interpolated value between and on a scale (generally) - /// between 0.0 and 1.0 defined by the - /// Note: The progress may go be negative or go beyond 1.0 depending on the easing method used - /// - /// The value to interpolate away from - /// The value to interpolate towards - /// The progress of the interpolation between 0.0 and 1.0 - /// - public abstract TProperty Interpolate(TProperty a, TProperty b, double progress); - - /// - /// Applies the to the layer property - /// - /// - public virtual void ApplyValue(TProperty value) - { - if (DataBinding?.Registration == null) - throw new ArtemisCoreException("Data binding converter is not yet initialized"); - DataBinding.Registration.Setter(value); - } - - /// - /// Returns the current base value of the data binding - /// - public virtual TProperty GetValue() - { - if (DataBinding?.Registration == null) - throw new ArtemisCoreException("Data binding converter is not yet initialized"); - return DataBinding.Registration.Getter(); - } - - /// - /// Converts the provided object to a type of - /// - public virtual TProperty ConvertFromObject(object? source) - { - return (TProperty) Convert.ChangeType(source, typeof(TProperty))!; - } - - /// - /// Called when the data binding converter has been initialized and the is available - /// - protected virtual void OnInitialized() - { - } - - internal void Initialize(DataBinding dataBinding) - { - if (dataBinding.Registration == null) - throw new ArtemisCoreException("Cannot initialize a data binding converter for a data binding without a registration"); - - DataBinding = dataBinding; - OnInitialized(); - } - - /// - public Type SupportedType => typeof(TProperty); - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingProperty.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingProperty.cs new file mode 100644 index 000000000..6c67300a2 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingProperty.cs @@ -0,0 +1,46 @@ +using System; + +namespace Artemis.Core +{ + /// + public class DataBindingProperty : IDataBindingProperty + { + internal DataBindingProperty(Func getter, Action setter, string displayName) + { + Getter = getter ?? throw new ArgumentNullException(nameof(getter)); + Setter = setter ?? throw new ArgumentNullException(nameof(setter)); + DisplayName = displayName ?? throw new ArgumentNullException(nameof(displayName)); + } + + /// + /// Gets the function to call to get the value of the property + /// + public Func Getter { get; } + + /// + /// Gets the action to call to set the value of the property + /// + public Action Setter { get; } + + /// + public string DisplayName { get; } + + /// + public Type ValueType => typeof(TProperty); + + /// + public object? GetValue() + { + return Getter(); + } + + /// + public void SetValue(object? value) + { + if (value is TProperty match) + Setter(match); + else + throw new ArgumentException("Value must match the type of the data binding registration", nameof(value)); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs deleted file mode 100644 index e7d5ca1d1..000000000 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System; -using System.Linq; -using Artemis.Storage.Entities.Profile.DataBindings; - -namespace Artemis.Core -{ - /// - public class DataBindingRegistration : IDataBindingRegistration - { - internal DataBindingRegistration(LayerProperty layerProperty, DataBindingConverter converter, - Func getter, Action setter, string displayName) - { - LayerProperty = layerProperty ?? throw new ArgumentNullException(nameof(layerProperty)); - Converter = converter ?? throw new ArgumentNullException(nameof(converter)); - Getter = getter ?? throw new ArgumentNullException(nameof(getter)); - Setter = setter ?? throw new ArgumentNullException(nameof(setter)); - DisplayName = displayName ?? throw new ArgumentNullException(nameof(displayName)); - } - - /// - /// Gets the layer property this registration was made on - /// - public LayerProperty LayerProperty { get; } - - /// - /// Gets the converter that's used by the data binding - /// - public DataBindingConverter Converter { get; } - - /// - /// Gets the function to call to get the value of the property - /// - public Func Getter { get; } - - /// - /// Gets the action to call to set the value of the property - /// - public Action Setter { get; } - - /// - public string DisplayName { get; } - - /// - public Type ValueType => typeof(TProperty); - - /// - /// Gets the data binding created using this registration - /// - public DataBinding? DataBinding { get; internal set; } - - /// - public IDataBinding? GetDataBinding() - { - return DataBinding; - } - - /// - public IDataBinding? CreateDataBinding() - { - if (DataBinding != null) - return DataBinding; - - DataBindingEntity? dataBinding = LayerProperty.Entity.DataBindingEntities.FirstOrDefault(e => e.Identifier == DisplayName); - if (dataBinding == null) - return null; - - DataBinding = new DataBinding(LayerProperty, dataBinding); - return DataBinding; - } - - /// - public void ClearDataBinding() - { - if (DataBinding == null) - return; - - // The related entity is left behind, just in case the data binding is added back later - LayerProperty.DisableDataBinding(DataBinding); - } - - /// - public object? GetValue() - { - return Getter(); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs index 3c7c995bd..e706a3701 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.ObjectModel; using Artemis.Core.Modules; namespace Artemis.Core @@ -7,22 +8,51 @@ namespace Artemis.Core /// Represents a data binding that binds a certain to a value inside a /// /// - public interface IDataBinding : IStorageModel, IUpdateModel, IDisposable + public interface IDataBinding : IStorageModel, IDisposable { /// - /// Updates the smoothing progress of the data binding and recalculates the value next call + /// Gets the layer property the data binding is applied to /// - /// The delta to apply during update - void UpdateWithDelta(TimeSpan delta); + ILayerProperty BaseLayerProperty { get; } /// - /// Applies the data binding to the layer property + /// Gets a list of sub-properties this data binding applies to + /// + ReadOnlyCollection Properties { get; } + + /// + /// Gets a boolean indicating whether the data binding is enabled or not + /// + bool IsEnabled { get; set; } + + /// + /// Applies the pending value of the data binding to the property /// void Apply(); - + /// /// If the data binding is enabled, loads the node script for that data binding /// void LoadNodeScript(); + + /// + /// Occurs when a data binding property has been added + /// + public event EventHandler? DataBindingPropertyRegistered; + + /// + /// Occurs when all data binding properties have been removed + /// + public event EventHandler? DataBindingPropertiesCleared; + + /// + /// Occurs when a data binding has been enabled + /// + public event EventHandler? DataBindingEnabled; + + /// + /// Occurs when a data binding has been disabled + /// + public event EventHandler? DataBindingDisabled; } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingConverter.cs b/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingConverter.cs deleted file mode 100644 index 9f207133f..000000000 --- a/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingConverter.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace Artemis.Core -{ - /// - /// Represents a data binding converter that acts as the bridge between a - /// and a - /// - public interface IDataBindingConverter - { - /// - /// Gets the type this converter supports - /// - public Type SupportedType { get; } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingRegistration.cs b/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingProperty.cs similarity index 55% rename from src/Artemis.Core/Models/Profile/DataBindings/IDataBindingRegistration.cs rename to src/Artemis.Core/Models/Profile/DataBindings/IDataBindingProperty.cs index 4b9ceeb94..c6f299766 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingRegistration.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingProperty.cs @@ -5,7 +5,7 @@ namespace Artemis.Core /// /// Represents a data binding registration /// - public interface IDataBindingRegistration + public interface IDataBindingProperty { /// /// Gets or sets the display name of the data binding registration @@ -18,25 +18,15 @@ namespace Artemis.Core Type ValueType { get; } /// - /// Returns the data binding applied using this registration - /// - public IDataBinding? GetDataBinding(); - - /// - /// If found, creates a data binding from storage - /// - /// - IDataBinding? CreateDataBinding(); - - /// - /// If present, removes the current data binding - /// - void ClearDataBinding(); - - /// - /// Gets the value of the data binding + /// Gets the value of the property this registration points to /// /// A value matching the type of object? GetValue(); + + /// + /// Sets the value of the property this registration points to + /// + /// A value matching the type of + void SetValue(object? value); } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs index dcfcdeeda..9391c1027 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using Artemis.Storage.Entities.Profile; namespace Artemis.Core @@ -23,6 +22,21 @@ namespace Artemis.Core /// LayerPropertyGroup LayerPropertyGroup { get; } + /// + /// Gets the data binding of this property + /// + IDataBinding BaseDataBinding { get; } + + /// + /// Gets a boolean indicating whether the layer has any data binding properties + /// + public bool HasDataBinding { get; } + + /// + /// Gets a boolean indicating whether data bindings are supported on this type of property + /// + public bool DataBindingsSupported { get; } + /// /// Gets the unique path of the property on the layer /// @@ -47,11 +61,6 @@ namespace Artemis.Core /// void Initialize(RenderProfileElement profileElement, LayerPropertyGroup group, PropertyEntity entity, bool fromStorage, PropertyDescriptionAttribute description, string path); - /// - /// Returns a list off all data binding registrations - /// - List GetAllDataBindingRegistrations(); - /// /// Attempts to load and add the provided keyframe entity to the layer property /// @@ -70,6 +79,12 @@ namespace Artemis.Core /// The timeline to apply to the property void Update(Timeline timeline); + + /// + /// Updates just the data binding instead of the entire layer + /// + void UpdateDataBinding(); + /// /// Occurs when the layer property is disposed /// @@ -104,25 +119,5 @@ namespace Artemis.Core /// Occurs when a keyframe was removed from the layer property /// public event EventHandler? KeyframeRemoved; - - /// - /// Occurs when a data binding property has been added - /// - public event EventHandler? DataBindingPropertyRegistered; - - /// - /// Occurs when all data binding properties have been removed - /// - public event EventHandler? DataBindingPropertiesCleared; - - /// - /// Occurs when a data binding has been enabled - /// - public event EventHandler? DataBindingEnabled; - - /// - /// Occurs when a data binding has been disabled - /// - public event EventHandler? DataBindingDisabled; } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs index 3d83ce37b..d75e47af0 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs @@ -30,6 +30,7 @@ namespace Artemis.Core Path = null!; Entity = null!; PropertyDescription = null!; + DataBinding = null!; CurrentValue = default!; DefaultValue = default!; @@ -57,93 +58,10 @@ namespace Artemis.Core { _disposed = true; - foreach (IDataBinding dataBinding in _dataBindings) - dataBinding.Dispose(); - + DataBinding.Dispose(); Disposed?.Invoke(this, EventArgs.Empty); } - /// - /// Invokes the event - /// - protected virtual void OnUpdated() - { - Updated?.Invoke(this, new LayerPropertyEventArgs(this)); - } - - /// - /// Invokes the event - /// - protected virtual void OnCurrentValueSet() - { - CurrentValueSet?.Invoke(this, new LayerPropertyEventArgs(this)); - LayerPropertyGroup.OnLayerPropertyOnCurrentValueSet(new LayerPropertyEventArgs(this)); - } - - /// - /// Invokes the event - /// - protected virtual void OnVisibilityChanged() - { - VisibilityChanged?.Invoke(this, new LayerPropertyEventArgs(this)); - } - - /// - /// Invokes the event - /// - protected virtual void OnKeyframesToggled() - { - KeyframesToggled?.Invoke(this, new LayerPropertyEventArgs(this)); - } - - /// - /// Invokes the event - /// - protected virtual void OnKeyframeAdded() - { - KeyframeAdded?.Invoke(this, new LayerPropertyEventArgs(this)); - } - - /// - /// Invokes the event - /// - protected virtual void OnKeyframeRemoved() - { - KeyframeRemoved?.Invoke(this, new LayerPropertyEventArgs(this)); - } - - /// - /// Invokes the event - /// - protected virtual void OnDataBindingPropertyRegistered() - { - DataBindingPropertyRegistered?.Invoke(this, new LayerPropertyEventArgs(this)); - } - - /// - /// Invokes the event - /// - protected virtual void OnDataBindingPropertiesCleared() - { - DataBindingPropertiesCleared?.Invoke(this, new LayerPropertyEventArgs(this)); - } - - /// - /// Invokes the event - /// - protected virtual void OnDataBindingEnabled(LayerPropertyEventArgs e) - { - DataBindingEnabled?.Invoke(this, e); - } - - /// - /// Invokes the event - /// - protected virtual void OnDataBindingDisabled(LayerPropertyEventArgs e) - { - DataBindingDisabled?.Invoke(this, e); - } - /// public PropertyDescriptionAttribute PropertyDescription { get; internal set; } @@ -162,7 +80,16 @@ namespace Artemis.Core CurrentValue = BaseValue; UpdateKeyframes(timeline); - UpdateDataBindings(timeline); + UpdateDataBinding(); + + // UpdateDataBinding called OnUpdated() + } + + /// + public void UpdateDataBinding() + { + DataBinding.Update(); + DataBinding.Apply(); OnUpdated(); } @@ -174,39 +101,6 @@ namespace Artemis.Core GC.SuppressFinalize(this); } - /// - public event EventHandler? Disposed; - - /// - public event EventHandler? Updated; - - /// - public event EventHandler? CurrentValueSet; - - /// - public event EventHandler? VisibilityChanged; - - /// - public event EventHandler? KeyframesToggled; - - /// - public event EventHandler? KeyframeAdded; - - /// - public event EventHandler? KeyframeRemoved; - - /// - public event EventHandler? DataBindingPropertyRegistered; - - /// - public event EventHandler? DataBindingPropertiesCleared; - - /// - public event EventHandler? DataBindingEnabled; - - /// - public event EventHandler? DataBindingDisabled; - #region Hierarchy private bool _isHidden; @@ -480,137 +374,19 @@ namespace Artemis.Core #region Data bindings - // ReSharper disable InconsistentNaming - internal readonly List _dataBindingRegistrations = new(); - - internal readonly List _dataBindings = new(); - // ReSharper restore InconsistentNaming - /// - /// Gets whether data bindings are supported on this type of property + /// Gets the data binding of this property /// - public bool DataBindingsSupported { get; protected internal set; } = true; + public DataBinding DataBinding { get; private set; } - /// - /// Gets whether the layer has any active data bindings - /// - public bool HasDataBinding => GetAllDataBindingRegistrations().Any(r => r.GetDataBinding() != null); + /// + public bool DataBindingsSupported => DataBinding.Properties.Any(); - /// - /// Gets a data binding registration by the display name used to register it - /// Note: The expression must exactly match the one used to register the data binding - /// - public DataBindingRegistration? GetDataBindingRegistration(string identifier) - { - if (_disposed) - throw new ObjectDisposedException("LayerProperty"); + /// + public IDataBinding BaseDataBinding => DataBinding; - IDataBindingRegistration? match = _dataBindingRegistrations.FirstOrDefault(r => r is DataBindingRegistration registration && - registration.DisplayName == identifier); - return (DataBindingRegistration?) match; - } - - /// - /// Gets a list containing all data binding registrations of this layer property - /// - /// A list containing all data binding registrations of this layer property - public List GetAllDataBindingRegistrations() - { - return _dataBindingRegistrations; - } - - /// - /// Registers a data binding property so that is available to the data binding system - /// - /// The type of the layer property - /// The function to call to get the value of the property - /// The action to call to set the value of the property - /// The converter to use while applying the data binding - /// The display name of the data binding property - public DataBindingRegistration RegisterDataBindingProperty(Func getter, Action setter, DataBindingConverter converter, - string displayName) - { - if (_disposed) - throw new ObjectDisposedException("LayerProperty"); - if (_dataBindingRegistrations.Any(d => d.DisplayName == displayName)) - throw new ArtemisCoreException($"A databinding property named '{displayName}' is already registered."); - - DataBindingRegistration registration = new(this, converter, getter, setter, displayName); - _dataBindingRegistrations.Add(registration); - - // If not yet initialized, load the data binding related to the registration if available - if (_isInitialized) - { - IDataBinding? dataBinding = registration.CreateDataBinding(); - if (dataBinding != null) - _dataBindings.Add(dataBinding); - } - - OnDataBindingPropertyRegistered(); - return registration; - } - - /// - /// Removes all data binding properties so they are no longer available to the data binding system - /// - public void ClearDataBindingProperties() - { - if (_disposed) - throw new ObjectDisposedException("LayerProperty"); - - foreach (IDataBindingRegistration dataBindingRegistration in _dataBindingRegistrations) - dataBindingRegistration.ClearDataBinding(); - _dataBindingRegistrations.Clear(); - - OnDataBindingPropertiesCleared(); - } - - /// - /// Enables a data binding for the provided - /// - /// The newly created data binding - public DataBinding EnableDataBinding(DataBindingRegistration dataBindingRegistration) - { - if (_disposed) - throw new ObjectDisposedException("LayerProperty"); - - if (dataBindingRegistration.LayerProperty != this) - throw new ArtemisCoreException("Cannot enable a data binding using a data binding registration of a different layer property"); - if (dataBindingRegistration.DataBinding != null) - throw new ArtemisCoreException("Provided data binding registration already has an enabled data binding"); - - DataBinding dataBinding = new(dataBindingRegistration); - _dataBindings.Add(dataBinding); - - OnDataBindingEnabled(new LayerPropertyEventArgs(dataBinding.LayerProperty)); - return dataBinding; - } - - /// - /// Disables the provided data binding - /// - /// The data binding to remove - public void DisableDataBinding(DataBinding dataBinding) - { - if (_disposed) - throw new ObjectDisposedException("LayerProperty"); - - _dataBindings.Remove(dataBinding); - - if (dataBinding.Registration != null) - dataBinding.Registration.DataBinding = null; - dataBinding.Dispose(); - OnDataBindingDisabled(new LayerPropertyEventArgs(dataBinding.LayerProperty)); - } - - private void UpdateDataBindings(Timeline timeline) - { - foreach (IDataBinding dataBinding in _dataBindings) - { - dataBinding.Update(timeline); - dataBinding.Apply(); - } - } + /// + public bool HasDataBinding => DataBinding.IsEnabled; #endregion @@ -694,6 +470,7 @@ namespace Artemis.Core Entity = entity ?? throw new ArgumentNullException(nameof(entity)); PropertyDescription = description ?? throw new ArgumentNullException(nameof(description)); IsLoadedFromStorage = fromStorage; + DataBinding = new DataBinding(this); if (PropertyDescription.DisableKeyframes) KeyframesSupported = false; @@ -737,13 +514,7 @@ namespace Artemis.Core // ignored for now } - _dataBindings.Clear(); - foreach (IDataBindingRegistration dataBindingRegistration in _dataBindingRegistrations) - { - IDataBinding? dataBinding = dataBindingRegistration.CreateDataBinding(); - if (dataBinding != null) - _dataBindings.Add(dataBinding); - } + DataBinding.Load(); } /// @@ -762,9 +533,8 @@ namespace Artemis.Core Entity.KeyframeEntities.Clear(); Entity.KeyframeEntities.AddRange(Keyframes.Select(k => k.GetKeyframeEntity())); - Entity.DataBindingEntities.Clear(); - foreach (IDataBinding dataBinding in _dataBindings) - dataBinding.Save(); + DataBinding.Save(); + Entity.DataBinding = DataBinding.Entity; } /// @@ -775,5 +545,79 @@ namespace Artemis.Core } #endregion + + #region Events + + /// + public event EventHandler? Disposed; + + /// + public event EventHandler? Updated; + + /// + public event EventHandler? CurrentValueSet; + + /// + public event EventHandler? VisibilityChanged; + + /// + public event EventHandler? KeyframesToggled; + + /// + public event EventHandler? KeyframeAdded; + + /// + public event EventHandler? KeyframeRemoved; + + /// + /// Invokes the event + /// + protected virtual void OnUpdated() + { + Updated?.Invoke(this, new LayerPropertyEventArgs(this)); + } + + /// + /// Invokes the event + /// + protected virtual void OnCurrentValueSet() + { + CurrentValueSet?.Invoke(this, new LayerPropertyEventArgs(this)); + LayerPropertyGroup.OnLayerPropertyOnCurrentValueSet(new LayerPropertyEventArgs(this)); + } + + /// + /// Invokes the event + /// + protected virtual void OnVisibilityChanged() + { + VisibilityChanged?.Invoke(this, new LayerPropertyEventArgs(this)); + } + + /// + /// Invokes the event + /// + protected virtual void OnKeyframesToggled() + { + KeyframesToggled?.Invoke(this, new LayerPropertyEventArgs(this)); + } + + /// + /// Invokes the event + /// + protected virtual void OnKeyframeAdded() + { + KeyframeAdded?.Invoke(this, new LayerPropertyEventArgs(this)); + } + + /// + /// Invokes the event + /// + protected virtual void OnKeyframeRemoved() + { + KeyframeRemoved?.Invoke(this, new LayerPropertyEventArgs(this)); + } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs index f6a87f5b0..8cef4a100 100644 --- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs @@ -113,11 +113,8 @@ namespace Artemis.Core ? new NodeScript($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active", RenderElementEntity.NodeScript, Profile) : new NodeScript($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active", Profile); - foreach (ILayerProperty layerProperty in GetAllLayerProperties()) - { - foreach (IDataBindingRegistration dataBindingRegistration in layerProperty.GetAllDataBindingRegistrations()) - dataBindingRegistration.GetDataBinding()?.LoadNodeScript(); - } + foreach (ILayerProperty layerProperty in GetAllLayerProperties()) + layerProperty.BaseDataBinding.LoadNodeScript(); } internal void OnLayerEffectsUpdated() diff --git a/src/Artemis.Core/VisualScripting/DataBindingNodeScript.cs b/src/Artemis.Core/VisualScripting/DataBindingNodeScript.cs new file mode 100644 index 000000000..198fa0cb8 --- /dev/null +++ b/src/Artemis.Core/VisualScripting/DataBindingNodeScript.cs @@ -0,0 +1,36 @@ +using System; +using Artemis.Core.Internal; +using Artemis.Storage.Entities.Profile.Nodes; + +namespace Artemis.Core +{ + public class DataBindingNodeScript : NodeScript + { + #region Properties & Fields + + internal DataBindingExitNode DataBindingExitNode { get; } + + /// + public override Type ResultType => typeof(object); + + #endregion + + /// + public DataBindingNodeScript(string name, string description, DataBinding dataBinding, object? context = null) + : base(name, description, context) + { + DataBindingExitNode = new DataBindingExitNode(dataBinding); + ExitNode = DataBindingExitNode; + AddNode(ExitNode); + } + + /// + public DataBindingNodeScript(string name, string description, DataBinding dataBinding, NodeScriptEntity entity, object? context = null) + : base(name, description, entity, context) + { + DataBindingExitNode = new DataBindingExitNode(dataBinding); + ExitNode = DataBindingExitNode; + AddNode(ExitNode); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/VisualScripting/InputPin.cs b/src/Artemis.Core/VisualScripting/InputPin.cs index 0244b6da3..39849a105 100644 --- a/src/Artemis.Core/VisualScripting/InputPin.cs +++ b/src/Artemis.Core/VisualScripting/InputPin.cs @@ -96,7 +96,7 @@ namespace Artemis.Core private void Evaluate() { - Value = ConnectedTo.Count > 0 ? ConnectedTo[0].PinValue : default; + Value = ConnectedTo.Count > 0 ? ConnectedTo[0].PinValue : Type.GetDefault(); } #endregion diff --git a/src/Artemis.Core/VisualScripting/Internal/DataBindingExitNode.cs b/src/Artemis.Core/VisualScripting/Internal/DataBindingExitNode.cs new file mode 100644 index 000000000..ef1102d85 --- /dev/null +++ b/src/Artemis.Core/VisualScripting/Internal/DataBindingExitNode.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Artemis.Core.Internal +{ + internal class DataBindingExitNode : Node, IExitNode + { + private readonly Dictionary _propertyPins = new(); + private readonly Dictionary _propertyValues = new(); + + public DataBinding DataBinding { get; } + + public DataBindingExitNode(DataBinding dataBinding) : base(dataBinding.LayerProperty.PropertyDescription.Name ?? "", "") + { + DataBinding = dataBinding; + DataBinding.DataBindingPropertiesCleared += DataBindingOnDataBindingPropertiesCleared; + DataBinding.DataBindingPropertyRegistered += DataBindingOnDataBindingPropertyRegistered; + + CreateInputPins(); + } + + public override bool IsExitNode => true; + + public override void Evaluate() + { + foreach (var (property, inputPin) in _propertyPins) + { + if (inputPin.ConnectedTo.Any()) + _propertyValues[property] = inputPin.Value; + else + _propertyValues.Remove(property); + } + } + + public void ApplyToDataBinding() + { + foreach (var (property, pendingValue) in _propertyValues) + property.SetValue(pendingValue); + } + + private void ClearInputPins() + { + while (Pins.Any()) + RemovePin((Pin) Pins.First()); + + _propertyPins.Clear(); + _propertyValues.Clear(); + } + + private void CreateInputPins() + { + ClearInputPins(); + + foreach (IDataBindingProperty property in DataBinding.Properties) + _propertyPins.Add(property, CreateInputPin(property.ValueType, property.DisplayName)); + } + + #region Event handlers + + private void DataBindingOnDataBindingPropertyRegistered(object? sender, DataBindingEventArgs e) + { + CreateInputPins(); + } + + private void DataBindingOnDataBindingPropertiesCleared(object? sender, DataBindingEventArgs e) + { + ClearInputPins(); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingEntity.cs b/src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingEntity.cs index 92a38fb1a..1160c38ae 100644 --- a/src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingEntity.cs @@ -1,14 +1,10 @@ -using System; -using Artemis.Storage.Entities.Profile.Nodes; +using Artemis.Storage.Entities.Profile.Nodes; namespace Artemis.Storage.Entities.Profile.DataBindings { public class DataBindingEntity { public string Identifier { get; set; } - public TimeSpan EasingTime { get; set; } - public int EasingFunction { get; set; } - public NodeScriptEntity NodeScript { get; set; } } } \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/PropertyEntity.cs b/src/Artemis.Storage/Entities/Profile/PropertyEntity.cs index 6c04025d4..ec5fc0344 100644 --- a/src/Artemis.Storage/Entities/Profile/PropertyEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/PropertyEntity.cs @@ -8,16 +8,15 @@ namespace Artemis.Storage.Entities.Profile public PropertyEntity() { KeyframeEntities = new List(); - DataBindingEntities = new List(); } public string FeatureId { get; set; } public string Path { get; set; } + public DataBindingEntity DataBinding { get; set; } public string Value { get; set; } public bool KeyframesEnabled { get; set; } public List KeyframeEntities { get; set; } - public List DataBindingEntities { get; set; } } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/PropertyInput/PropertyInputViewModel.cs b/src/Artemis.UI.Shared/PropertyInput/PropertyInputViewModel.cs index a5e043888..f03156dd0 100644 --- a/src/Artemis.UI.Shared/PropertyInput/PropertyInputViewModel.cs +++ b/src/Artemis.UI.Shared/PropertyInput/PropertyInputViewModel.cs @@ -44,6 +44,11 @@ namespace Artemis.UI.Shared /// public LayerProperty LayerProperty { get; } + /// + /// Gets a boolean indicating whether the layer property should be enabled + /// + public bool IsEnabled => !LayerProperty.HasDataBinding; + /// /// Gets the profile editor service /// @@ -85,8 +90,8 @@ namespace Artemis.UI.Shared { LayerProperty.Updated += LayerPropertyOnUpdated; LayerProperty.CurrentValueSet += LayerPropertyOnUpdated; - LayerProperty.DataBindingEnabled += LayerPropertyOnDataBindingChange; - LayerProperty.DataBindingDisabled += LayerPropertyOnDataBindingChange; + LayerProperty.DataBinding.DataBindingEnabled += OnDataBindingChange; + LayerProperty.DataBinding.DataBindingDisabled += OnDataBindingChange; UpdateInputValue(); base.OnInitialActivate(); } @@ -96,8 +101,8 @@ namespace Artemis.UI.Shared { LayerProperty.Updated -= LayerPropertyOnUpdated; LayerProperty.CurrentValueSet -= LayerPropertyOnUpdated; - LayerProperty.DataBindingEnabled -= LayerPropertyOnDataBindingChange; - LayerProperty.DataBindingDisabled -= LayerPropertyOnDataBindingChange; + LayerProperty.DataBinding.DataBindingEnabled -= OnDataBindingChange; + LayerProperty.DataBinding.DataBindingDisabled -= OnDataBindingChange; base.OnClose(); } @@ -194,8 +199,9 @@ namespace Artemis.UI.Shared UpdateInputValue(); } - private void LayerPropertyOnDataBindingChange(object? sender, LayerPropertyEventArgs e) + private void OnDataBindingChange(object? sender, DataBindingEventArgs e) { + NotifyOfPropertyChange(nameof(IsEnabled)); OnDataBindingsChanged(); } diff --git a/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs b/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs index d0fdb6e3e..6f1b604eb 100644 --- a/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs @@ -37,9 +37,9 @@ namespace Artemis.UI.Shared.Services RenderProfileElement? SelectedProfileElement { get; } /// - /// Gets the currently selected data binding property + /// Gets the currently selected data binding /// - ILayerProperty? SelectedDataBinding { get; } + IDataBinding? SelectedDataBinding { get; } /// /// Gets or sets the current time @@ -89,10 +89,10 @@ namespace Artemis.UI.Shared.Services void SaveSelectedProfileElement(); /// - /// Changes the selected data binding property + /// Changes the selected data binding /// - /// The data binding property to select - void ChangeSelectedDataBinding(ILayerProperty? layerProperty); + /// The data binding to select + void ChangeSelectedDataBinding(IDataBinding? dataBinding); /// /// Updates the profile preview, forcing UI-elements to re-render @@ -114,7 +114,7 @@ namespace Artemis.UI.Shared.Services /// /// Registers a new property input view model used in the profile editor for the generic type defined in /// - /// Note: Registration will remove itself on plugin disable so you don't have to + /// Note: DataBindingProperty will remove itself on plugin disable so you don't have to /// /// /// @@ -123,7 +123,7 @@ namespace Artemis.UI.Shared.Services /// /// Registers a new property input view model used in the profile editor for the generic type defined in /// - /// Note: Registration will remove itself on plugin disable so you don't have to + /// Note: DataBindingProperty will remove itself on plugin disable so you don't have to /// /// /// diff --git a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs index 37cccd627..b93760af4 100644 --- a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs @@ -115,7 +115,7 @@ namespace Artemis.UI.Shared.Services // Trigger selected data binding change if (SelectedDataBinding != null) { - SelectedDataBinding = SelectedProfileElement?.GetAllLayerProperties().FirstOrDefault(p => p.Path == SelectedDataBinding.Path); + SelectedDataBinding = SelectedProfileElement?.GetAllLayerProperties().FirstOrDefault(p => p.Path == SelectedDataBinding.BaseLayerProperty.Path)?.BaseDataBinding; OnSelectedDataBindingChanged(); } @@ -184,7 +184,7 @@ namespace Artemis.UI.Shared.Services public ProfileConfiguration? SelectedProfileConfiguration { get; private set; } public Profile? SelectedProfile => SelectedProfileConfiguration?.Profile; public RenderProfileElement? SelectedProfileElement { get; private set; } - public ILayerProperty? SelectedDataBinding { get; private set; } + public IDataBinding? SelectedDataBinding { get; private set; } public TimeSpan CurrentTime { @@ -303,9 +303,9 @@ namespace Artemis.UI.Shared.Services } } - public void ChangeSelectedDataBinding(ILayerProperty? layerProperty) + public void ChangeSelectedDataBinding(IDataBinding? dataBinding) { - SelectedDataBinding = layerProperty; + SelectedDataBinding = dataBinding; OnSelectedDataBindingChanged(); } diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputViewModel.cs b/src/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputViewModel.cs index 1a3472a72..806d52084 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputViewModel.cs +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputViewModel.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using System.Linq; -using Artemis.Core; +using Artemis.Core; using Artemis.UI.Shared; using Artemis.UI.Shared.Services; @@ -8,14 +6,10 @@ namespace Artemis.UI.DefaultTypes.PropertyInput { public class BoolPropertyInputViewModel : PropertyInputViewModel { - private List _registrations; - public BoolPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService) : base(layerProperty, profileEditorService) { - _registrations = layerProperty.GetAllDataBindingRegistrations(); } - public bool IsEnabled => _registrations.Any(r => r.GetDataBinding() != null); protected override void OnDataBindingsChanged() { diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.xaml index 75b8ae201..09f2f664a 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.xaml @@ -5,7 +5,6 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:dataTemplateSelectors="clr-namespace:Artemis.UI.DataTemplateSelectors" - xmlns:layerBrush="clr-namespace:Artemis.Core.LayerBrushes;assembly=Artemis.Core" xmlns:propertyInput="clr-namespace:Artemis.UI.DefaultTypes.PropertyInput" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs b/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs index bafcc7f30..bdea29e96 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs @@ -13,8 +13,8 @@ namespace Artemis.UI.DefaultTypes.PropertyInput { public class BrushPropertyInputViewModel : PropertyInputViewModel { - private readonly IPluginManagementService _pluginManagementService; private readonly IDialogService _dialogService; + private readonly IPluginManagementService _pluginManagementService; private BindableCollection _descriptors; public BrushPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, IPluginManagementService pluginManagementService, @@ -51,13 +51,11 @@ namespace Artemis.UI.DefaultTypes.PropertyInput { layer.ChangeLayerBrush(SelectedDescriptor); if (layer.LayerBrush?.Presets != null && layer.LayerBrush.Presets.Any()) - { Execute.PostToUIThread(async () => { await Task.Delay(400); await _dialogService.ShowDialogAt("LayerProperties", new Dictionary {{"layerBrush", layer.LayerBrush}}); }); - } } } diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputViewModel.cs b/src/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputViewModel.cs index c028b564d..f01f483ec 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputViewModel.cs +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputViewModel.cs @@ -8,19 +8,9 @@ namespace Artemis.UI.DefaultTypes.PropertyInput { public class FloatPropertyInputViewModel : PropertyInputViewModel { - private readonly DataBindingRegistration _registration; - public FloatPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, IModelValidator validator) : base(layerProperty, profileEditorService, validator) { - _registration = layerProperty.GetDataBindingRegistration("Value"); - } - - public bool IsEnabled => _registration.DataBinding == null; - - protected override void OnDataBindingsChanged() - { - NotifyOfPropertyChange(nameof(IsEnabled)); } } diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputView.xaml index c2c5d3501..cbb7dc9c5 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputView.xaml @@ -27,7 +27,7 @@ Min="{Binding Start}" DragStarted="{s:Action InputDragStarted}" DragEnded="{s:Action InputDragEnded}" - IsEnabled="{Binding IsEndEnabled}" /> + IsEnabled="{Binding IsEnabled}" /> \ No newline at end of file diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputViewModel.cs b/src/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputViewModel.cs index 20195e150..2a1271ad5 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputViewModel.cs +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputViewModel.cs @@ -9,15 +9,10 @@ namespace Artemis.UI.DefaultTypes.PropertyInput { public class FloatRangePropertyInputViewModel : PropertyInputViewModel { - private readonly DataBindingRegistration _startRegistration; - private readonly DataBindingRegistration _endRegistration; - public FloatRangePropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, IModelValidator validator) : base(layerProperty, profileEditorService, validator) { - _startRegistration = layerProperty.GetDataBindingRegistration("Start"); - _endRegistration = layerProperty.GetDataBindingRegistration("End"); } public float Start @@ -48,20 +43,12 @@ namespace Artemis.UI.DefaultTypes.PropertyInput } } - public bool IsStartEnabled => _startRegistration.DataBinding == null; - public bool IsEndEnabled => _endRegistration.DataBinding == null; protected override void OnInputValueChanged() { NotifyOfPropertyChange(nameof(Start)); NotifyOfPropertyChange(nameof(End)); } - - protected override void OnDataBindingsChanged() - { - NotifyOfPropertyChange(nameof(IsStartEnabled)); - NotifyOfPropertyChange(nameof(IsEndEnabled)); - } } public class FloatRangePropertyInputViewModelValidator : AbstractValidator diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputViewModel.cs b/src/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputViewModel.cs index f729ef554..f972d5e3f 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputViewModel.cs +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputViewModel.cs @@ -8,19 +8,9 @@ namespace Artemis.UI.DefaultTypes.PropertyInput { public class IntPropertyInputViewModel : PropertyInputViewModel { - private readonly DataBindingRegistration _registration; - public IntPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, IModelValidator validator) : base(layerProperty, profileEditorService, validator) { - _registration = layerProperty.GetDataBindingRegistration("Value"); - } - - public bool IsEnabled => _registration.DataBinding == null; - - protected override void OnDataBindingsChanged() - { - NotifyOfPropertyChange(nameof(IsEnabled)); } } diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputView.xaml index da9440ce1..8c38caabf 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputView.xaml @@ -18,7 +18,7 @@ Min="{Binding LayerProperty.PropertyDescription.MinInputValue}" DragStarted="{s:Action InputDragStarted}" DragEnded="{s:Action InputDragEnded}" - IsEnabled="{Binding IsStartEnabled}" /> + IsEnabled="{Binding IsEnabled}" /> - + IsEnabled="{Binding IsEnabled}" /> \ No newline at end of file diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputViewModel.cs b/src/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputViewModel.cs index eed3e04ff..0358e383f 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputViewModel.cs +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputViewModel.cs @@ -9,15 +9,10 @@ namespace Artemis.UI.DefaultTypes.PropertyInput { public class IntRangePropertyInputViewModel : PropertyInputViewModel { - private readonly DataBindingRegistration _startRegistration; - private readonly DataBindingRegistration _endRegistration; - public IntRangePropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, IModelValidator validator) : base(layerProperty, profileEditorService, validator) { - _startRegistration = layerProperty.GetDataBindingRegistration("Start"); - _endRegistration = layerProperty.GetDataBindingRegistration("End"); } public int Start @@ -48,20 +43,12 @@ namespace Artemis.UI.DefaultTypes.PropertyInput } } - public bool IsStartEnabled => _startRegistration.DataBinding == null; - public bool IsEndEnabled => _endRegistration.DataBinding == null; protected override void OnInputValueChanged() { NotifyOfPropertyChange(nameof(Start)); NotifyOfPropertyChange(nameof(End)); } - - protected override void OnDataBindingsChanged() - { - NotifyOfPropertyChange(nameof(IsStartEnabled)); - NotifyOfPropertyChange(nameof(IsEndEnabled)); - } } public class IntRangePropertyInputViewModelValidator : AbstractValidator diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.xaml index 026d4d381..bc2c308f9 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.xaml @@ -17,10 +17,10 @@ + DragEnded="{s:Action InputDragEnded}" /> \ No newline at end of file diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputViewModel.cs b/src/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputViewModel.cs index 9bc2cc186..f2fb56579 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputViewModel.cs +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputViewModel.cs @@ -7,18 +7,8 @@ namespace Artemis.UI.DefaultTypes.PropertyInput { public class SKColorPropertyInputViewModel : PropertyInputViewModel { - private readonly DataBindingRegistration _registration; - public SKColorPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService) : base(layerProperty, profileEditorService) { - _registration = layerProperty.GetDataBindingRegistration("Value"); - } - - public bool IsEnabled => _registration.DataBinding == null; - - protected override void OnDataBindingsChanged() - { - NotifyOfPropertyChange(nameof(IsEnabled)); } } } \ No newline at end of file diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.xaml index 06cf966ca..55282ff67 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.xaml @@ -18,7 +18,7 @@ Min="{Binding LayerProperty.PropertyDescription.MinInputValue}" DragStarted="{s:Action InputDragStarted}" DragEnded="{s:Action InputDragEnded}" - IsEnabled="{Binding IsXEnabled}" /> + IsEnabled="{Binding IsEnabled}" /> , + IsEnabled="{Binding IsEnabled}" /> \ No newline at end of file diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputViewModel.cs b/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputViewModel.cs index ac32cbaf3..b4b4e4860 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputViewModel.cs +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputViewModel.cs @@ -10,14 +10,9 @@ namespace Artemis.UI.DefaultTypes.PropertyInput { public class SKPointPropertyInputViewModel : PropertyInputViewModel { - private readonly DataBindingRegistration _xRegistration; - private readonly DataBindingRegistration _yRegistration; - public SKPointPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, IModelValidator validator) : base(layerProperty, profileEditorService, validator) { - _xRegistration = layerProperty.GetDataBindingRegistration("X"); - _yRegistration = layerProperty.GetDataBindingRegistration("Y"); } public float X @@ -32,20 +27,12 @@ namespace Artemis.UI.DefaultTypes.PropertyInput set => InputValue = new SKPoint(X, value); } - public bool IsXEnabled => _xRegistration.DataBinding == null; - public bool IsYEnabled => _yRegistration.DataBinding == null; protected override void OnInputValueChanged() { NotifyOfPropertyChange(nameof(X)); NotifyOfPropertyChange(nameof(Y)); } - - protected override void OnDataBindingsChanged() - { - NotifyOfPropertyChange(nameof(IsXEnabled)); - NotifyOfPropertyChange(nameof(IsYEnabled)); - } } public class SKPointPropertyInputViewModelValidator : AbstractValidator diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputView.xaml index a15a2b38f..9fbf36a0c 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputView.xaml @@ -18,7 +18,7 @@ Min="{Binding LayerProperty.PropertyDescription.MinInputValue}" DragStarted="{s:Action InputDragStarted}" DragEnded="{s:Action InputDragEnded}" - IsEnabled="{Binding IsHeightEnabled}" /> + IsEnabled="{Binding IsEnabled}" /> , + IsEnabled="{Binding IsEnabled}" /> \ No newline at end of file diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputViewModel.cs b/src/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputViewModel.cs index 48707f5c3..023cc700c 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputViewModel.cs +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputViewModel.cs @@ -12,14 +12,9 @@ namespace Artemis.UI.DefaultTypes.PropertyInput { public class SKSizePropertyInputViewModel : PropertyInputViewModel { - private readonly DataBindingRegistration _heightRegistration; - private readonly DataBindingRegistration _widthRegistration; - public SKSizePropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, IModelValidator validator) : base(layerProperty, profileEditorService, validator) { - _widthRegistration = layerProperty.GetDataBindingRegistration("Width"); - _heightRegistration = layerProperty.GetDataBindingRegistration("Height"); } // Since SKSize is immutable we need to create properties that replace the SKSize entirely @@ -35,20 +30,12 @@ namespace Artemis.UI.DefaultTypes.PropertyInput set => InputValue = new SKSize(Width, value); } - public bool IsWidthEnabled => _widthRegistration.DataBinding == null; - public bool IsHeightEnabled => _heightRegistration.DataBinding == null; protected override void OnInputValueChanged() { NotifyOfPropertyChange(nameof(Width)); NotifyOfPropertyChange(nameof(Height)); } - - protected override void OnDataBindingsChanged() - { - NotifyOfPropertyChange(nameof(IsWidthEnabled)); - NotifyOfPropertyChange(nameof(IsHeightEnabled)); - } } public class SKSizePropertyInputViewModelValidator : AbstractValidator diff --git a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs index 7d1c85aa3..aaf677e09 100644 --- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -116,11 +116,6 @@ namespace Artemis.UI.Ninject.Factories NodeScriptWindowViewModel NodeScriptWindowViewModel(NodeScript nodeScript); } - public interface IDataBindingsVmFactory - { - IDataBindingViewModel DataBindingViewModel(IDataBindingRegistration registration); - } - public interface IPropertyVmFactory { ITreePropertyViewModel TreePropertyViewModel(ILayerProperty layerProperty, LayerPropertyViewModel layerPropertyViewModel); diff --git a/src/Artemis.UI/Ninject/InstanceProviders/DataBindingsViewModelInstanceProvider.cs b/src/Artemis.UI/Ninject/InstanceProviders/DataBindingsViewModelInstanceProvider.cs deleted file mode 100644 index 3ab21713f..000000000 --- a/src/Artemis.UI/Ninject/InstanceProviders/DataBindingsViewModelInstanceProvider.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Reflection; -using Artemis.Core; -using Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings; -using Ninject.Extensions.Factory; - -namespace Artemis.UI.Ninject.InstanceProviders -{ - public class DataBindingsViewModelInstanceProvider : StandardInstanceProvider - { - protected override Type GetType(MethodInfo methodInfo, object[] arguments) - { - if (methodInfo.ReturnType != typeof(IDataBindingViewModel)) - return base.GetType(methodInfo, arguments); - - // Find LayerProperty type - Type descriptionPropertyType = arguments[0].GetType(); - while (descriptionPropertyType != null && (!descriptionPropertyType.IsGenericType || descriptionPropertyType.GetGenericTypeDefinition() != typeof(DataBindingRegistration<,>))) - descriptionPropertyType = descriptionPropertyType.BaseType; - if (descriptionPropertyType == null) - return base.GetType(methodInfo, arguments); - - return typeof(DataBindingViewModel<,>).MakeGenericType(descriptionPropertyType.GetGenericArguments()); - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Ninject/UiModule.cs b/src/Artemis.UI/Ninject/UiModule.cs index 549b170c2..02a66a68f 100644 --- a/src/Artemis.UI/Ninject/UiModule.cs +++ b/src/Artemis.UI/Ninject/UiModule.cs @@ -56,7 +56,6 @@ namespace Artemis.UI.Ninject .BindToFactory(); }); - Kernel.Bind().ToFactory(() => new DataBindingsViewModelInstanceProvider()); Kernel.Bind().ToFactory(() => new LayerPropertyViewModelInstanceProvider()); // Bind profile editor VMs diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml deleted file mode 100644 index be5b8b20e..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - Enable data binding - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Result - - - Other bindings not updating? - - - - - - - - - - - - Input - - - Output - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml.cs deleted file mode 100644 index 2808d7a97..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Windows.Controls; - -namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings -{ - /// - /// Interaction logic for DataBindingView.xaml - /// - public partial class DataBindingView : UserControl - { - public DataBindingView() - { - InitializeComponent(); - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs deleted file mode 100644 index 9b0a82562..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs +++ /dev/null @@ -1,251 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Artemis.Core; -using Artemis.Core.Services; -using Artemis.Storage.Entities.Profile.DataBindings; -using Artemis.UI.Exceptions; -using Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline; -using Artemis.UI.Shared; -using Artemis.UI.Shared.Services; -using Stylet; - -namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings -{ - public sealed class DataBindingViewModel : Screen, IDataBindingViewModel - { - private readonly ICoreService _coreService; - private readonly IProfileEditorService _profileEditorService; - private int _easingTime; - private bool _isDataBindingEnabled; - private bool _isEasingTimeEnabled; - private DateTime _lastUpdate; - private TimelineEasingViewModel _selectedEasingViewModel; - - private bool _updating; - private bool _updatingTestResult; - - public DataBindingViewModel(DataBindingRegistration registration, - ICoreService coreService, - ISettingsService settingsService, - IProfileEditorService profileEditorService, - IDataModelUIService dataModelUIService, - INodeService nodeService) - { - Registration = registration; - _coreService = coreService; - _profileEditorService = profileEditorService; - - DisplayName = Registration.DisplayName.ToUpper(); - AlwaysApplyDataBindings = settingsService.GetSetting("ProfileEditor.AlwaysApplyDataBindings", true); - EasingViewModels = new BindableCollection(); - AvailableNodes = nodeService.AvailableNodes; - TestInputValue = dataModelUIService.GetDataModelDisplayViewModel(typeof(TProperty), null, true); - TestResultValue = dataModelUIService.GetDataModelDisplayViewModel(typeof(TProperty), null, true); - } - - public DataBindingRegistration Registration { get; } - public NodeScript Script => Registration.DataBinding?.Script; - - public PluginSetting AlwaysApplyDataBindings { get; } - public BindableCollection EasingViewModels { get; } - public IEnumerable AvailableNodes { get; } - public DataModelDisplayViewModel TestInputValue { get; } - public DataModelDisplayViewModel TestResultValue { get; } - - - public bool IsDataBindingEnabled - { - get => _isDataBindingEnabled; - set - { - SetAndNotify(ref _isDataBindingEnabled, value); - if (_updating) - return; - - if (value) - EnableDataBinding(); - else - DisableDataBinding(); - } - } - - public TimelineEasingViewModel SelectedEasingViewModel - { - get => _selectedEasingViewModel; - set - { - if (!SetAndNotify(ref _selectedEasingViewModel, value)) return; - ApplyChanges(); - } - } - - public int EasingTime - { - get => _easingTime; - set - { - if (!SetAndNotify(ref _easingTime, value)) return; - ApplyChanges(); - } - } - - public bool IsEasingTimeEnabled - { - get => _isEasingTimeEnabled; - set - { - if (!SetAndNotify(ref _isEasingTimeEnabled, value)) return; - ApplyChanges(); - } - } - - public void CopyDataBinding() - { - if (Registration.DataBinding != null) - JsonClipboard.SetObject(Registration.DataBinding.Entity); - } - - public void PasteDataBinding() - { - if (Registration.DataBinding == null) - Registration.LayerProperty.EnableDataBinding(Registration); - if (Registration.DataBinding == null) - throw new ArtemisUIException("Failed to create a data binding in order to paste"); - - DataBindingEntity dataBindingEntity = JsonClipboard.GetData(); - if (dataBindingEntity == null) - return; - - Registration.DataBinding.EasingTime = dataBindingEntity.EasingTime; - Registration.DataBinding.EasingFunction = (Easings.Functions) dataBindingEntity.EasingFunction; - - // TODO - Paste visual script - - Update(); - - _profileEditorService.SaveSelectedProfileElement(); - } - - protected override void OnInitialActivate() - { - Initialize(); - base.OnInitialActivate(); - } - - private void Initialize() - { - EasingViewModels.AddRange(Enum.GetValues(typeof(Easings.Functions)).Cast().Select(v => new TimelineEasingViewModel(v, false))); - - _lastUpdate = DateTime.Now; - _coreService.FrameRendered += OnFrameRendered; - Update(); - } - - private void Update() - { - if (_updating) - return; - - if (Registration.DataBinding == null) - { - IsDataBindingEnabled = false; - IsEasingTimeEnabled = false; - return; - } - - _updating = true; - - EasingTime = (int) Registration.DataBinding.EasingTime.TotalMilliseconds; - SelectedEasingViewModel = EasingViewModels.First(vm => vm.EasingFunction == Registration.DataBinding.EasingFunction); - IsDataBindingEnabled = true; - IsEasingTimeEnabled = EasingTime > 0; - - _updating = false; - } - - private void ApplyChanges() - { - if (_updating) - return; - - if (Registration.DataBinding != null) - { - Registration.DataBinding.EasingTime = TimeSpan.FromMilliseconds(EasingTime); - Registration.DataBinding.EasingFunction = SelectedEasingViewModel?.EasingFunction ?? Easings.Functions.Linear; - } - - _profileEditorService.SaveSelectedProfileElement(); - Update(); - } - - private void UpdateTestResult() - { - if (_updating || _updatingTestResult) - return; - - _updatingTestResult = true; - - if (Registration.DataBinding == null) - { - TestInputValue.UpdateValue(default); - TestResultValue.UpdateValue(default); - _updatingTestResult = false; - return; - } - - // If always update is disabled, do constantly update the data binding as long as the view model is open - // If always update is enabled, this is done for all data bindings in ProfileViewModel - if (!AlwaysApplyDataBindings.Value) - { - Registration.DataBinding.UpdateWithDelta(DateTime.Now - _lastUpdate); - _profileEditorService.UpdateProfilePreview(); - } - - TProperty currentValue = Registration.Converter.ConvertFromObject(Registration.DataBinding.Script.Result ?? default(TProperty)); - TestInputValue.UpdateValue(currentValue); - TestResultValue.UpdateValue(Registration.DataBinding != null ? Registration.DataBinding.GetValue(currentValue) : default); - - _updatingTestResult = false; - } - - private void EnableDataBinding() - { - if (Registration.DataBinding != null) - return; - - Registration.LayerProperty.EnableDataBinding(Registration); - NotifyOfPropertyChange(nameof(Script)); - - _profileEditorService.SaveSelectedProfileElement(); - } - - private void DisableDataBinding() - { - if (Registration.DataBinding == null) - return; - - Registration.LayerProperty.DisableDataBinding(Registration.DataBinding); - NotifyOfPropertyChange(nameof(Script)); - Update(); - - _profileEditorService.SaveSelectedProfileElement(); - } - - private void OnFrameRendered(object sender, FrameRenderedEventArgs e) - { - UpdateTestResult(); - _lastUpdate = DateTime.Now; - } - - /// - public void Dispose() - { - _coreService.FrameRendered -= OnFrameRendered; - } - } - - public interface IDataBindingViewModel : IScreen, IDisposable - { - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsView.xaml index 164f28796..90da2f349 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsView.xaml @@ -4,20 +4,48 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:s="https://github.com/canton7/Stylet" + xmlns:controls="clr-namespace:Artemis.VisualScripting.Editor.Controls;assembly=Artemis.VisualScripting" + xmlns:layerProperties="clr-namespace:Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings" + xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" mc:Ignorable="d" - d:DesignHeight="450" d:DesignWidth="800"> - - - - - - - - - - - - + d:DesignHeight="450" d:DesignWidth="800" + d:DataContext="{d:DesignInstance {x:Type layerProperties:DataBindingsViewModel}}"> + + + + + + + + + + + + + Enable data binding + + + + + + + + + Enable this data binding to use nodes! + + + When you enable data bindings you can no longer use keyframes or normal values for this property. + Instead you'll be using our node scripting system to set a value based on the data model or values of other layers/folders. + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs index aee7265a0..a2b4d0846 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs @@ -1,88 +1,68 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; using Artemis.Core; -using Artemis.UI.Ninject.Factories; +using Artemis.Core.Services; using Artemis.UI.Shared.Services; using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings { - public class DataBindingsViewModel : Conductor.Collection.AllActive + public class DataBindingsViewModel : Screen { - private readonly IDataBindingsVmFactory _dataBindingsVmFactory; private readonly IProfileEditorService _profileEditorService; - private ILayerProperty _selectedDataBinding; - private int _selectedItemIndex; - private bool _updating; + private IDataBinding _dataBinding; - public DataBindingsViewModel(IProfileEditorService profileEditorService, IDataBindingsVmFactory dataBindingsVmFactory) + public DataBindingsViewModel(IProfileEditorService profileEditorService, INodeService nodeService) { _profileEditorService = profileEditorService; - _dataBindingsVmFactory = dataBindingsVmFactory; + AvailableNodes = nodeService.AvailableNodes.ToList(); } - public int SelectedItemIndex + public List AvailableNodes { get; } + + public IDataBinding DataBinding { - get => _selectedItemIndex; - set => SetAndNotify(ref _selectedItemIndex, value); + get => _dataBinding; + set => SetAndNotify(ref _dataBinding, value); } - private void CreateDataBindingViewModels() + public bool DataBindingEnabled { - int oldIndex = SelectedItemIndex; - Items.Clear(); - - ILayerProperty layerProperty = _profileEditorService.SelectedDataBinding; - if (layerProperty == null) - return; - - List registrations = layerProperty.GetAllDataBindingRegistrations(); - - // Create a data binding VM for each data bindable property. These VMs will be responsible for retrieving - // and creating the actual data bindings - Items.AddRange(registrations.Select(registration => _dataBindingsVmFactory.DataBindingViewModel(registration))); - - SelectedItemIndex = Items.Count < oldIndex ? 0 : oldIndex; + get => _dataBinding?.IsEnabled ?? false; + set + { + if (_dataBinding != null) + _dataBinding.IsEnabled = value; + } } private void ProfileEditorServiceOnSelectedDataBindingChanged(object sender, EventArgs e) { - CreateDataBindingViewModels(); SubscribeToSelectedDataBinding(); - - SelectedItemIndex = 0; } private void SubscribeToSelectedDataBinding() { - if (_selectedDataBinding != null) + if (DataBinding != null) { - _selectedDataBinding.DataBindingPropertyRegistered -= DataBindingRegistrationsChanged; - _selectedDataBinding.DataBindingPropertiesCleared -= DataBindingRegistrationsChanged; + DataBinding.DataBindingEnabled -= DataBindingOnDataBindingToggled; + DataBinding.DataBindingDisabled -= DataBindingOnDataBindingToggled; } - _selectedDataBinding = _profileEditorService.SelectedDataBinding; - if (_selectedDataBinding != null) + DataBinding = _profileEditorService.SelectedDataBinding; + if (DataBinding != null) { - _selectedDataBinding.DataBindingPropertyRegistered += DataBindingRegistrationsChanged; - _selectedDataBinding.DataBindingPropertiesCleared += DataBindingRegistrationsChanged; + DataBinding.DataBindingEnabled += DataBindingOnDataBindingToggled; + DataBinding.DataBindingDisabled += DataBindingOnDataBindingToggled; + + OnPropertyChanged(nameof(DataBindingEnabled)); } } - private void DataBindingRegistrationsChanged(object sender, LayerPropertyEventArgs e) + private void DataBindingOnDataBindingToggled(object? sender, DataBindingEventArgs e) { - if (_updating) - return; - - _updating = true; - Execute.PostToUIThread(async () => - { - await Task.Delay(200); - CreateDataBindingViewModels(); - _updating = false; - }); + OnPropertyChanged(nameof(DataBindingEnabled)); } #region Overrides of Screen @@ -90,17 +70,10 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings protected override void OnInitialActivate() { _profileEditorService.SelectedDataBindingChanged += ProfileEditorServiceOnSelectedDataBindingChanged; - CreateDataBindingViewModels(); SubscribeToSelectedDataBinding(); base.OnInitialActivate(); } - protected override void OnActivate() - { - SelectedItemIndex = 0; - base.OnActivate(); - } - protected override void OnClose() { _profileEditorService.SelectedDataBindingChanged -= ProfileEditorServiceOnSelectedDataBindingChanged; diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesView.xaml index 12344aec7..9d0ea97d9 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesView.xaml @@ -156,7 +156,6 @@ - diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs index e79e4325b..93a2e4d3b 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs @@ -39,10 +39,10 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree public void ActivateDataBindingViewModel() { - if (_profileEditorService.SelectedDataBinding == LayerProperty) + if (_profileEditorService.SelectedDataBinding == LayerProperty.DataBinding) _profileEditorService.ChangeSelectedDataBinding(null); else - _profileEditorService.ChangeSelectedDataBinding(LayerProperty); + _profileEditorService.ChangeSelectedDataBinding(LayerProperty.DataBinding); } public void ResetToDefault() @@ -92,8 +92,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree { _profileEditorService.SelectedDataBindingChanged += ProfileEditorServiceOnSelectedDataBindingChanged; LayerProperty.VisibilityChanged += LayerPropertyOnVisibilityChanged; - LayerProperty.DataBindingEnabled += LayerPropertyOnDataBindingChange; - LayerProperty.DataBindingDisabled += LayerPropertyOnDataBindingChange; + LayerProperty.DataBinding.DataBindingEnabled += LayerPropertyOnDataBindingChange; + LayerProperty.DataBinding.DataBindingDisabled += LayerPropertyOnDataBindingChange; LayerProperty.KeyframesToggled += LayerPropertyOnKeyframesToggled; LayerPropertyViewModel.IsVisible = !LayerProperty.IsHidden; base.OnInitialActivate(); @@ -104,8 +104,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree { _profileEditorService.SelectedDataBindingChanged -= ProfileEditorServiceOnSelectedDataBindingChanged; LayerProperty.VisibilityChanged -= LayerPropertyOnVisibilityChanged; - LayerProperty.DataBindingEnabled -= LayerPropertyOnDataBindingChange; - LayerProperty.DataBindingDisabled -= LayerPropertyOnDataBindingChange; + LayerProperty.DataBinding.DataBindingEnabled -= LayerPropertyOnDataBindingChange; + LayerProperty.DataBinding.DataBindingDisabled -= LayerPropertyOnDataBindingChange; LayerProperty.KeyframesToggled -= LayerPropertyOnKeyframesToggled; base.OnClose(); } @@ -116,7 +116,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree private void ProfileEditorServiceOnSelectedDataBindingChanged(object sender, EventArgs e) { - LayerPropertyViewModel.IsHighlighted = _profileEditorService.SelectedDataBinding == LayerProperty; + LayerPropertyViewModel.IsHighlighted = _profileEditorService.SelectedDataBinding == LayerProperty.DataBinding; } private void LayerPropertyOnVisibilityChanged(object sender, LayerPropertyEventArgs e) @@ -124,7 +124,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree LayerPropertyViewModel.IsVisible = !LayerProperty.IsHidden; } - private void LayerPropertyOnDataBindingChange(object sender, LayerPropertyEventArgs e) + private void LayerPropertyOnDataBindingChange(object sender, DataBindingEventArgs e) { NotifyOfPropertyChange(nameof(HasDataBinding)); } diff --git a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs index b0549cdd7..40f94cf4e 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs @@ -30,7 +30,6 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization private bool _canSelectEditTool; private BindableCollection _devices; private BindableCollection _highlightedLeds; - private DateTime _lastUpdate; private PanZoomViewModel _panZoomViewModel; private Layer _previousSelectedLayer; private int _previousTool; @@ -138,7 +137,6 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization ApplyActiveProfile(); - _lastUpdate = DateTime.Now; _coreService.FrameRendered += OnFrameRendered; _rgbService.DeviceAdded += RgbServiceOnDevicesModified; @@ -232,7 +230,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization if (CanSelectEditTool == false && ActiveToolIndex == 1) ActivateToolByIndex(2); } - + #region Buttons private void ActivateToolByIndex(int value) @@ -307,19 +305,32 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization private void OnFrameRendered(object sender, FrameRenderedEventArgs e) { - TimeSpan delta = DateTime.Now - _lastUpdate; - _lastUpdate = DateTime.Now; - - if (SuspendedEditing || !_settingsService.GetSetting("ProfileEditor.AlwaysApplyDataBindings", true).Value || _profileEditorService.SelectedProfile == null) + if (SuspendedEditing || _profileEditorService.Playing || _profileEditorService.SelectedProfile == null) return; - foreach (IDataBindingRegistration dataBindingRegistration in _profileEditorService.SelectedProfile.GetAllRenderElements() - .SelectMany(f => f.GetAllLayerProperties(), (f, p) => p) - .SelectMany(p => p.GetAllDataBindingRegistrations())) - dataBindingRegistration.GetDataBinding()?.UpdateWithDelta(delta); - - // TODO: Only update when there are data bindings - if (!_profileEditorService.Playing) + bool hasEnabledDataBindings = false; + + // Always update the currently selected data binding + if (_profileEditorService.SelectedDataBinding != null && _profileEditorService.SelectedDataBinding.IsEnabled) + { + hasEnabledDataBindings = true; + _profileEditorService.SelectedDataBinding.BaseLayerProperty.UpdateDataBinding(); + } + + // Update all other data bindings if the user enabled this + if (_settingsService.GetSetting("ProfileEditor.AlwaysApplyDataBindings", true).Value) + { + foreach (ILayerProperty layerProperty in _profileEditorService.SelectedProfile.GetAllRenderElements().SelectMany(f => f.GetAllLayerProperties(), (_, p) => p)) + { + if (layerProperty.BaseDataBinding != _profileEditorService.SelectedDataBinding && layerProperty.BaseDataBinding.IsEnabled) + { + hasEnabledDataBindings = true; + layerProperty.UpdateDataBinding(); + } + } + } + + if (hasEnabledDataBindings) _profileEditorService.UpdateProfilePreview(); } diff --git a/src/Artemis.VisualScripting/Nodes/CustomViewModels/LayerPropertyNodeCustomViewModel.cs b/src/Artemis.VisualScripting/Nodes/CustomViewModels/LayerPropertyNodeCustomViewModel.cs index 05706be9a..f18667794 100644 --- a/src/Artemis.VisualScripting/Nodes/CustomViewModels/LayerPropertyNodeCustomViewModel.cs +++ b/src/Artemis.VisualScripting/Nodes/CustomViewModels/LayerPropertyNodeCustomViewModel.cs @@ -61,7 +61,7 @@ namespace Artemis.VisualScripting.Nodes.CustomViewModels if (_node.ProfileElement == null) return; - LayerProperties.AddRange(_node.ProfileElement.GetAllLayerProperties().Where(l => l.GetAllDataBindingRegistrations().Any())); + LayerProperties.AddRange(_node.ProfileElement.GetAllLayerProperties().Where(l => l.DataBindingsSupported)); _selectedLayerProperty = _node.LayerProperty; NotifyOfPropertyChange(nameof(SelectedLayerProperty)); } diff --git a/src/Artemis.VisualScripting/Nodes/LayerPropertyNode.cs b/src/Artemis.VisualScripting/Nodes/LayerPropertyNode.cs index 64f75d022..e07c0b07c 100644 --- a/src/Artemis.VisualScripting/Nodes/LayerPropertyNode.cs +++ b/src/Artemis.VisualScripting/Nodes/LayerPropertyNode.cs @@ -26,16 +26,16 @@ namespace Artemis.VisualScripting.Nodes return; } - List list = LayerProperty.GetAllDataBindingRegistrations(); + List list = LayerProperty.BaseDataBinding.Properties.ToList(); int index = 0; foreach (IPin pin in Pins) { OutputPin outputPin = (OutputPin) pin; - IDataBindingRegistration dataBindingRegistration = list[index]; + IDataBindingProperty dataBindingProperty = list[index]; index++; // TODO: Is this really non-nullable? - outputPin.Value = dataBindingRegistration.GetValue(); + outputPin.Value = dataBindingProperty.GetValue(); } } } @@ -108,7 +108,7 @@ namespace Artemis.VisualScripting.Nodes if (LayerProperty == null) return; - foreach (IDataBindingRegistration dataBindingRegistration in LayerProperty.GetAllDataBindingRegistrations()) + foreach (IDataBindingProperty dataBindingRegistration in LayerProperty.BaseDataBinding.Properties) CreateOutputPin(dataBindingRegistration.ValueType, dataBindingRegistration.DisplayName); } From 9d6a61f1e51e18c9fa02b0db138a4f4d63a948da Mon Sep 17 00:00:00 2001 From: Robert Date: Sun, 22 Aug 2021 22:31:47 +0200 Subject: [PATCH 23/47] Nodes - Added color nodes --- .../Profile/LayerProperties/ILayerProperty.cs | 5 ++ .../Profile/LayerProperties/LayerProperty.cs | 4 +- .../VisualScripting/NodeScript.cs | 20 ++++--- .../Services/RegistrationService.cs | 1 + src/Artemis.UI/packages.lock.json | 5 +- .../Artemis.VisualScripting.csproj | 1 + .../Styles/VisualScriptCablePresenter.xaml | 56 +++++++++++++++---- .../Nodes/Color/BrightenSKColorNode.cs | 27 +++++++++ .../StaticSKColorValueNodeCustomViewModel.cs | 38 +++++++++++++ .../StaticSKColorValueNodeCustomView.xaml | 16 ++++++ .../Nodes/Color/DarkenSKColorNode.cs | 27 +++++++++ .../Nodes/Color/DesaturateSKColorNode.cs | 29 ++++++++++ .../Nodes/Color/HslSKColorNode.cs | 32 +++++++++++ .../Nodes/Color/InvertSKColorNode.cs | 25 +++++++++ .../Nodes/Color/RotateHueSkColorNode.cs | 27 +++++++++ .../Nodes/Color/SaturateSkColorNode.cs | 29 ++++++++++ .../Nodes/Color/StaticSKColorValueNode.cs | 43 ++++++++++++++ .../Nodes/Color/SumSKColorsNode.cs | 46 +++++++++++++++ .../LayerPropertyNodeCustomViewModel.cs | 6 +- .../packages.lock.json | 17 +++--- 20 files changed, 420 insertions(+), 34 deletions(-) create mode 100644 src/Artemis.VisualScripting/Nodes/Color/BrightenSKColorNode.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Color/CustomViewModels/StaticSKColorValueNodeCustomViewModel.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Color/CustomViews/StaticSKColorValueNodeCustomView.xaml create mode 100644 src/Artemis.VisualScripting/Nodes/Color/DarkenSKColorNode.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Color/DesaturateSKColorNode.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Color/HslSKColorNode.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Color/InvertSKColorNode.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Color/RotateHueSkColorNode.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Color/SaturateSkColorNode.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Color/StaticSKColorValueNode.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Color/SumSKColorsNode.cs diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs index 9391c1027..52ae56bf1 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs @@ -22,6 +22,11 @@ namespace Artemis.Core /// LayerPropertyGroup LayerPropertyGroup { get; } + /// + /// Gets or sets whether the property is hidden in the UI + /// + public bool IsHidden { get; set; } + /// /// Gets the data binding of this property /// diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs index d75e47af0..059707618 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs @@ -105,9 +105,7 @@ namespace Artemis.Core private bool _isHidden; - /// - /// Gets or sets whether the property is hidden in the UI - /// + /// public bool IsHidden { get => _isHidden; diff --git a/src/Artemis.Core/VisualScripting/NodeScript.cs b/src/Artemis.Core/VisualScripting/NodeScript.cs index 0094d8daa..faeac5616 100644 --- a/src/Artemis.Core/VisualScripting/NodeScript.cs +++ b/src/Artemis.Core/VisualScripting/NodeScript.cs @@ -178,9 +178,13 @@ namespace Artemis.Core Entity.Description = Description; Entity.Nodes.Clear(); - int id = 0; + + // No need to save the exit node if that's all there is + if (Nodes.Count() == 1) + return; - Dictionary nodes = new(); + int id = 0; + foreach (INode node in Nodes) { NodeEntity nodeEntity = new() @@ -208,7 +212,6 @@ namespace Artemis.Core } Entity.Nodes.Add(nodeEntity); - nodes.Add(node, id); id++; } @@ -216,20 +219,21 @@ namespace Artemis.Core Entity.Connections.Clear(); foreach (INode node in Nodes) { - SavePins(nodes, node, -1, node.Pins); + SavePins(node, -1, node.Pins); int pinCollectionId = 0; foreach (IPinCollection pinCollection in node.PinCollections) { - SavePins(nodes, node, pinCollectionId, pinCollection); + SavePins(node, pinCollectionId, pinCollection); pinCollectionId++; } } } - private void SavePins(Dictionary nodes, INode node, int collectionId, IEnumerable pins) + private void SavePins(INode node, int collectionId, IEnumerable pins) { int sourcePinId = 0; + List nodes = Nodes.ToList(); foreach (IPin sourcePin in pins.Where(p => p.Direction == PinDirection.Input)) { foreach (IPin targetPin in sourcePin.ConnectedTo) @@ -249,11 +253,11 @@ namespace Artemis.Core Entity.Connections.Add(new NodeConnectionEntity() { SourceType = sourcePin.Type.Name, - SourceNode = nodes[node], + SourceNode = nodes.IndexOf(node), SourcePinCollectionId = collectionId, SourcePinId = sourcePinId, TargetType = targetPin.Type.Name, - TargetNode = nodes[targetPin.Node], + TargetNode = nodes.IndexOf(targetPin.Node), TargetPinCollectionId = targetPinCollectionId, TargetPinId = targetPinId, }); diff --git a/src/Artemis.UI/Services/RegistrationService.cs b/src/Artemis.UI/Services/RegistrationService.cs index 5adbd456b..820ff6fd5 100644 --- a/src/Artemis.UI/Services/RegistrationService.cs +++ b/src/Artemis.UI/Services/RegistrationService.cs @@ -126,6 +126,7 @@ namespace Artemis.UI.Services _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(0xFF7630C7)); _nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(IList), new SKColor(0xFFC842FF)); foreach (Type nodeType in typeof(SumIntegersNode).Assembly.GetTypes().Where(t => typeof(INode).IsAssignableFrom(t) && t.IsPublic && !t.IsAbstract && !t.IsInterface)) diff --git a/src/Artemis.UI/packages.lock.json b/src/Artemis.UI/packages.lock.json index 4a5da0c7b..2a76c380f 100644 --- a/src/Artemis.UI/packages.lock.json +++ b/src/Artemis.UI/packages.lock.json @@ -535,8 +535,8 @@ }, "SkiaSharp": { "type": "Transitive", - "resolved": "2.80.2", - "contentHash": "D25rzdCwh+3L+XyXqpNa+H/yiLJbE3/R3K/XexwHyQjGdzZvSufFW3oqf3En7hhqSIsxsJ8f5NEZ0J5W5wlGBg==", + "resolved": "2.80.3", + "contentHash": "qX6tGNP3+MXNYe2pKm0PCRiJ/cx+LTeLaggwZifB7sUMXhECfKKKHJq45VqZKt37xQegnCCdf1jHXwmHeJQs5Q==", "dependencies": { "System.Memory": "4.5.3" } @@ -1484,6 +1484,7 @@ "Artemis.Core": "1.0.0", "Artemis.UI.Shared": "2.0.0", "JetBrains.Annotations": "2021.1.0", + "SkiaSharp": "2.80.3", "Stylet": "1.3.6" } } diff --git a/src/Artemis.VisualScripting/Artemis.VisualScripting.csproj b/src/Artemis.VisualScripting/Artemis.VisualScripting.csproj index 85b4c6b8e..987d69f58 100644 --- a/src/Artemis.VisualScripting/Artemis.VisualScripting.csproj +++ b/src/Artemis.VisualScripting/Artemis.VisualScripting.csproj @@ -37,6 +37,7 @@ + diff --git a/src/Artemis.VisualScripting/Editor/Styles/VisualScriptCablePresenter.xaml b/src/Artemis.VisualScripting/Editor/Styles/VisualScriptCablePresenter.xaml index 223a2b870..57770438c 100644 --- a/src/Artemis.VisualScripting/Editor/Styles/VisualScriptCablePresenter.xaml +++ b/src/Artemis.VisualScripting/Editor/Styles/VisualScriptCablePresenter.xaml @@ -3,14 +3,16 @@ xmlns:system="clr-namespace:System;assembly=System.Runtime" xmlns:controls="clr-namespace:Artemis.VisualScripting.Editor.Controls" xmlns:converters="clr-namespace:Artemis.VisualScripting.Converters" - xmlns:collections="clr-namespace:System.Collections;assembly=System.Runtime"> - + xmlns:collections="clr-namespace:System.Collections;assembly=System.Runtime" + xmlns:skiaSharp="clr-namespace:SkiaSharp;assembly=SkiaSharp" + xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"> + @@ -21,8 +23,8 @@ - @@ -36,26 +38,60 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + - + + + + + + + + Click to edit script + + + + + + + + + + + + Configure how the layer should act when the event(s) trigger + + + + + + + + + + + + + + + + + RESTART + + + + + Stop the current run and restart the timeline + + + + + + + + TOGGLE + + + + + Repeat the timeline until the event fires again + + + + + + + + IGNORE + + + + + Ignore subsequent event fires until the timeline finishes + + + + + + + + COPY + + + + + Play another copy of the timeline on top of the current run + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionEventViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionEventViewModel.cs new file mode 100644 index 000000000..0c0a97b2c --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionEventViewModel.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Stylet; + +namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions +{ + public class DisplayConditionEventViewModel : Screen + { + } +} diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml index 44bbe92a3..abaa2d145 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml @@ -25,269 +25,253 @@ - + Display conditions - - Not applied during editing - + + + + + + + + + + + + A condition that constantly checks and is either 'on' or 'off' + + + + + + CONSTANT + + + + + + + A condition that checks whenever one or more events fire and acts in a configurable way + + + + + + EVENTS + + + + - - - - - - - - - - - - - - - - Click to edit script - - - - - - - + - - - - - - - - - - - - - - Configure how the layer should act while the conditions above are met - - - - - - - - - - - - - - - Continue repeating the main segment of the timeline while the condition is met - - - - - - REPEAT - - - - - - - Only play the timeline once when the condition is met - - - - - - ONCE - - - - - - - - - - - Configure how the layer should act when the conditions above are no longer met - - - - - - - - - - - - - - - When conditions are no longer met, finish the the current run of the main timeline - - - - - - FINISH - - - - - - - When conditions are no longer met, skip to the end segment of the timeline - - - - - - SKIP TO END - - - - - - - - - - + + - - - - - - Configure how the layer should act when the event(s) trigger + + + + + + + + + + + + + + + + Click to edit script - - - - - - - - - - - - - - - RESTART - - - - - Stop the current run and restart the timeline - - - - - - - - TOGGLE - - - - - Repeat the timeline until the event fires again - - - - - - - - IGNORE - - - - - Ignore subsequent event fires until the timeline finishes - - - - - - - - COPY - - - - - Play another copy of the timeline on top of the current run - - - - - - + + + + + + + + + + + + + + + + + + + + Configure how the layer should act while the conditions above are met + + + + + + + + + + + + + + + Continue repeating the main segment of the timeline while the condition is met + + + + + + REPEAT + + + + + + + Only play the timeline once when the condition is met + + + + + + ONCE + + + + + + + + + + + Configure how the layer should act when the conditions above are no longer met + + + + + + + + + + + + + + + When conditions are no longer met, finish the the current run of the main timeline + + + + + + FINISH + + + + + + + When conditions are no longer met, skip to the end segment of the timeline + + + + + + SKIP TO END + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs index 72f4bfb91..640d283cd 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs @@ -8,7 +8,7 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions { - public class DisplayConditionsViewModel : Screen, IProfileEditorPanelViewModel + public class DisplayConditionsViewModel : Conductor.Collection.OneActive, IProfileEditorPanelViewModel { private readonly INodeVmFactory _nodeVmFactory; private readonly IProfileEditorService _profileEditorService; @@ -21,6 +21,11 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions _profileEditorService = profileEditorService; _windowManager = windowManager; _nodeVmFactory = nodeVmFactory; + + Items.Add(new DisplayConditionEventViewModel() { DisplayName = "Event 1"}); + Items.Add(new DisplayConditionEventViewModel() { DisplayName = "Event 2"}); + Items.Add(new DisplayConditionEventViewModel() { DisplayName = "Event 3"}); + Items.Add(new DisplayConditionEventViewModel() { DisplayName = "Event 4"}); } public bool IsEventCondition diff --git a/src/Artemis.UI/packages.lock.json b/src/Artemis.UI/packages.lock.json index 0845176f7..53f59c83c 100644 --- a/src/Artemis.UI/packages.lock.json +++ b/src/Artemis.UI/packages.lock.json @@ -1492,6 +1492,7 @@ "Artemis.Core": "1.0.0", "Artemis.UI.Shared": "2.0.0", "JetBrains.Annotations": "2021.1.0", + "MaterialDesignThemes": "4.1.0", "Ninject": "3.3.4", "NoStringEvaluating": "2.2.1", "SkiaSharp": "2.80.3", diff --git a/src/Artemis.VisualScripting/Artemis.VisualScripting.csproj b/src/Artemis.VisualScripting/Artemis.VisualScripting.csproj index da0811361..215c601ae 100644 --- a/src/Artemis.VisualScripting/Artemis.VisualScripting.csproj +++ b/src/Artemis.VisualScripting/Artemis.VisualScripting.csproj @@ -62,6 +62,9 @@ $(DefaultXamlRuntime) Designer + + $(DefaultXamlRuntime) + $(DefaultXamlRuntime) diff --git a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPresenter.cs b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPresenter.cs index 1e75ba72e..3860f1ebb 100644 --- a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPresenter.cs +++ b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPresenter.cs @@ -198,8 +198,6 @@ namespace Artemis.VisualScripting.Editor.Controls _canvas.RenderTransform = _canvasViewPortTransform = new TranslateTransform(0, 0); _canvas.MouseLeftButtonDown += OnCanvasMouseLeftButtonDown; _canvas.MouseLeftButtonUp += OnCanvasMouseLeftButtonUp; - _canvas.MouseRightButtonDown += OnCanvasMouseRightButtonDown; - _canvas.MouseRightButtonUp += OnCanvasMouseRightButtonUp; _canvas.PreviewMouseRightButtonDown += OnCanvasPreviewMouseRightButtonDown; _canvas.MouseMove += OnCanvasMouseMove; _canvas.MouseWheel += OnCanvasMouseWheel; @@ -292,6 +290,27 @@ namespace Artemis.VisualScripting.Editor.Controls private void OnCanvasMouseLeftButtonDown(object sender, MouseButtonEventArgs args) { + if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) + { + if (AutoFitScript) + { + args.Handled = true; + return; + } + + _dragCanvas = true; + _dragCanvasStartLocation = args.GetPosition(this); + _dragCanvasStartOffset = _viewportCenter; + + _movedDuringDrag = false; + + _canvas.CaptureMouse(); + + args.Handled = true; + + return; + } + VisualScript.DeselectAllNodes(); _boxSelect = true; @@ -317,29 +336,7 @@ namespace Artemis.VisualScripting.Editor.Controls SelectWithinRectangle(new Rect(Canvas.GetLeft(_selectionBorder), Canvas.GetTop(_selectionBorder), _selectionBorder.Width, _selectionBorder.Height)); args.Handled = _movedDuringDrag; } - } - private void OnCanvasMouseRightButtonDown(object sender, MouseButtonEventArgs args) - { - if (AutoFitScript) - { - args.Handled = true; - return; - } - - _dragCanvas = true; - _dragCanvasStartLocation = args.GetPosition(this); - _dragCanvasStartOffset = _viewportCenter; - - _movedDuringDrag = false; - - _canvas.CaptureMouse(); - - args.Handled = true; - } - - private void OnCanvasMouseRightButtonUp(object sender, MouseButtonEventArgs args) - { if (_dragCanvas) { _dragCanvas = false; @@ -353,7 +350,7 @@ namespace Artemis.VisualScripting.Editor.Controls { if (_dragCanvas) { - if (args.RightButton == MouseButtonState.Pressed) + if (args.LeftButton == MouseButtonState.Pressed && (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))) { Vector newLocation = _dragCanvasStartOffset - (((args.GetPosition(this) - _dragCanvasStartLocation)) * (1.0 / Scale)); CenterAt(newLocation); @@ -541,7 +538,7 @@ namespace Artemis.VisualScripting.Editor.Controls 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(); - + IPin preferredPin = SourcePin.Pin.Direction == PinDirection.Input ? pins.FirstOrDefault(p => p.Direction == PinDirection.Output) : pins.FirstOrDefault(p => p.Direction == PinDirection.Input); diff --git a/src/Artemis.VisualScripting/Nodes/DataModel/CustomViewModels/DataModelEventNodeCustomViewModel.cs b/src/Artemis.VisualScripting/Nodes/DataModel/CustomViewModels/DataModelEventNodeCustomViewModel.cs new file mode 100644 index 000000000..0057dcbea --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/DataModel/CustomViewModels/DataModelEventNodeCustomViewModel.cs @@ -0,0 +1,74 @@ +using System; +using System.ComponentModel; +using Artemis.Core; +using Artemis.Core.Modules; +using Artemis.Core.Services; +using Stylet; + +namespace Artemis.VisualScripting.Nodes.CustomViewModels +{ + public class DataModelEventNodeCustomViewModel : CustomNodeViewModel + { + private readonly DataModelEventNode _node; + private BindableCollection _modules; + + public DataModelEventNodeCustomViewModel(DataModelEventNode node, ISettingsService settingsService) : base(node) + { + _node = node; + + ShowFullPaths = settingsService.GetSetting("ProfileEditor.ShowFullPaths", true); + ShowDataModelValues = settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false); + } + + public PluginSetting ShowFullPaths { get; } + public PluginSetting ShowDataModelValues { get; } + public BindableCollection FilterTypes { get; } = new() { typeof(DataModelEvent) }; + + public BindableCollection Modules + { + get => _modules; + set => SetAndNotify(ref _modules, value); + } + + public DataModelPath DataModelPath + { + get => _node.DataModelPath; + set + { + if (ReferenceEquals(_node.DataModelPath, value)) + return; + + _node.DataModelPath?.Dispose(); + _node.DataModelPath = value; + _node.DataModelPath.Save(); + + _node.Storage = _node.DataModelPath.Entity; + } + } + + public override void OnActivate() + { + if (Modules != null) + return; + + Modules = new BindableCollection(); + if (_node.Script.Context is Profile scriptProfile && scriptProfile.Configuration.Module != null) + Modules.Add(scriptProfile.Configuration.Module); + else if (_node.Script.Context is ProfileConfiguration profileConfiguration && profileConfiguration.Module != null) + Modules.Add(profileConfiguration.Module); + + _node.PropertyChanged += NodeOnPropertyChanged; + } + + public override void OnDeactivate() + { + _node.PropertyChanged -= NodeOnPropertyChanged; + } + + private void NodeOnPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(DataModelNode.DataModelPath)) + OnPropertyChanged(nameof(DataModelPath)); + } + } +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/CustomViewModels/DataModelNodeCustomViewModel.cs b/src/Artemis.VisualScripting/Nodes/DataModel/CustomViewModels/DataModelNodeCustomViewModel.cs similarity index 100% rename from src/Artemis.VisualScripting/Nodes/CustomViewModels/DataModelNodeCustomViewModel.cs rename to src/Artemis.VisualScripting/Nodes/DataModel/CustomViewModels/DataModelNodeCustomViewModel.cs diff --git a/src/Artemis.VisualScripting/Nodes/DataModel/CustomViews/DataModelEventNodeCustomView.xaml b/src/Artemis.VisualScripting/Nodes/DataModel/CustomViews/DataModelEventNodeCustomView.xaml new file mode 100644 index 000000000..52708c600 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/DataModel/CustomViews/DataModelEventNodeCustomView.xaml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/src/Artemis.VisualScripting/Nodes/CustomViews/DataModelNodeCustomView.xaml b/src/Artemis.VisualScripting/Nodes/DataModel/CustomViews/DataModelNodeCustomView.xaml similarity index 100% rename from src/Artemis.VisualScripting/Nodes/CustomViews/DataModelNodeCustomView.xaml rename to src/Artemis.VisualScripting/Nodes/DataModel/CustomViews/DataModelNodeCustomView.xaml diff --git a/src/Artemis.VisualScripting/Nodes/DataModelEventNode.cs b/src/Artemis.VisualScripting/Nodes/DataModelEventNode.cs new file mode 100644 index 000000000..26c981864 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/DataModelEventNode.cs @@ -0,0 +1,51 @@ +using System; +using Artemis.Core; +using Artemis.Storage.Entities.Profile; +using Artemis.VisualScripting.Nodes.CustomViewModels; + +namespace Artemis.VisualScripting.Nodes +{ + [Node("Data Model-Event", "Responds to a data model event trigger", "External", OutputType = typeof(bool))] + public class DataModelEventNode : Node, IDisposable + { + private DataModelPath _dataModelPath; + + public DataModelEventNode() : base("Data Model-Event", "Responds to a data model event trigger") + { + Output = CreateOutputPin(); + } + + public OutputPin Output { get; } + public INodeScript Script { get; set; } + + public DataModelPath DataModelPath + { + get => _dataModelPath; + set => SetAndNotify(ref _dataModelPath, value); + } + + public override void Initialize(INodeScript script) + { + Script = script; + + if (Storage is not DataModelPathEntity pathEntity) + return; + + DataModelPath = new DataModelPath(null, pathEntity); + DataModelPath.PathValidated += DataModelPathOnPathValidated; + } + + public override void Evaluate() + { + } + + private void DataModelPathOnPathValidated(object sender, EventArgs e) + { + } + + /// + public void Dispose() + { + } + } +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/DataModelNode.cs b/src/Artemis.VisualScripting/Nodes/DataModelNode.cs index ee40fea9f..155420fc4 100644 --- a/src/Artemis.VisualScripting/Nodes/DataModelNode.cs +++ b/src/Artemis.VisualScripting/Nodes/DataModelNode.cs @@ -84,7 +84,7 @@ namespace Artemis.VisualScripting.Nodes /// public void Dispose() { - DataModelPath.Dispose(); + DataModelPath?.Dispose(); } #endregion From 8ac1431a2f6ea9ac9ab6f8087adc65dabeab8497 Mon Sep 17 00:00:00 2001 From: Robert Date: Sun, 12 Sep 2021 22:08:34 +0200 Subject: [PATCH 34/47] Core - Split up layer conditions into static- and event-conditions --- .../Artemis.Core.csproj.DotSettings | 1 + .../Profile/Conditions/EventCondition.cs | 98 ++++++++++ .../Profile/Conditions/EventsCondition.cs | 174 ++++++++++++++++++ .../Models/Profile/Conditions/ICondition.cs | 51 +++++ .../Profile/Conditions/StaticCondition.cs | 101 ++++++++++ .../Models/Profile/RenderProfileElement.cs | 100 +++------- .../Profile/Abstract/RenderElementEntity.cs | 2 +- .../CategoryAdaptionHintEntity.cs | 2 - .../DataModelConditionEventEntity.cs | 15 -- .../DataModelConditionEventPredicateEntity.cs | 6 - ...ataModelConditionGeneralPredicateEntity.cs | 6 - .../DataModelConditionGroupEntity.cs | 15 -- .../DataModelConditionListEntity.cs | 16 -- .../DataModelConditionListPredicateEntity.cs | 6 - .../DataModelConditionPredicateEntity.cs | 18 -- .../Conditions/EventsConditionEntity.cs | 18 ++ .../Profile/Conditions/IConditionEntity.cs | 6 + .../Conditions/StaticConditionEntity.cs | 10 + .../DisplayConditionsViewModel.cs | 2 +- 19 files changed, 491 insertions(+), 156 deletions(-) create mode 100644 src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs create mode 100644 src/Artemis.Core/Models/Profile/Conditions/EventsCondition.cs create mode 100644 src/Artemis.Core/Models/Profile/Conditions/ICondition.cs create mode 100644 src/Artemis.Core/Models/Profile/Conditions/StaticCondition.cs delete mode 100644 src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionEventEntity.cs delete mode 100644 src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionEventPredicateEntity.cs delete mode 100644 src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionGeneralPredicateEntity.cs delete mode 100644 src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionGroupEntity.cs delete mode 100644 src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionListEntity.cs delete mode 100644 src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionListPredicateEntity.cs delete mode 100644 src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionPredicateEntity.cs create mode 100644 src/Artemis.Storage/Entities/Profile/Conditions/EventsConditionEntity.cs create mode 100644 src/Artemis.Storage/Entities/Profile/Conditions/IConditionEntity.cs create mode 100644 src/Artemis.Storage/Entities/Profile/Conditions/StaticConditionEntity.cs diff --git a/src/Artemis.Core/Artemis.Core.csproj.DotSettings b/src/Artemis.Core/Artemis.Core.csproj.DotSettings index 224b124a9..535046f29 100644 --- a/src/Artemis.Core/Artemis.Core.csproj.DotSettings +++ b/src/Artemis.Core/Artemis.Core.csproj.DotSettings @@ -44,6 +44,7 @@ True True True + True True True True diff --git a/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs b/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs new file mode 100644 index 000000000..ad7913b54 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs @@ -0,0 +1,98 @@ +using System; +using Artemis.Storage.Entities.Profile.Conditions; + +namespace Artemis.Core +{ + public class EventCondition : CorePropertyChanged, IDisposable, IStorageModel + { + private readonly string _displayName; + private readonly object? _context; + private DateTime _lastProcessedTrigger; + private DataModelPath _eventPath; + + internal EventCondition(string displayName, object? context) + { + _displayName = displayName; + _context = context; + + Entity = new EventConditionEntity(); + Script = new NodeScript($"Activate {displayName}", $"Whether or not the event should activate the {displayName}", context); + } + + internal EventCondition(EventConditionEntity entity, string displayName, object? context) + { + _displayName = displayName; + _context = context; + + Entity = entity; + Script = null!; + + Load(); + } + + /// + /// Gets the script that drives the event condition + /// + public NodeScript Script { get; private set; } + + /// + /// Gets or sets the path to the event that drives this event condition + /// + public DataModelPath EventPath + { + set => SetAndNotify(ref _eventPath, value); + get => _eventPath; + } + + internal EventConditionEntity Entity { get; } + + internal bool Evaluate() + { + if (EventPath.GetValue() is not DataModelEvent dataModelEvent || dataModelEvent.LastTrigger <= _lastProcessedTrigger) + return false; + + // TODO: Place dataModelEvent.LastEventArgumentsUntyped; in the start node + Script.Run(); + + _lastProcessedTrigger = dataModelEvent.LastTrigger; + + return Script.Result; + } + + #region IDisposable + + /// + public void Dispose() + { + Script.Dispose(); + EventPath.Dispose(); + } + + #endregion + + internal void LoadNodeScript() + { + Script.Load(); + } + + #region Implementation of IStorageModel + + /// + public void Load() + { + EventPath = new DataModelPath(null, Entity.EventPath); + Script = new NodeScript($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", Entity.Script, _context); + } + + /// + public void Save() + { + EventPath.Save(); + Entity.EventPath = EventPath.Entity; + Script.Save(); + Entity.Script = Script.Entity; + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/EventsCondition.cs b/src/Artemis.Core/Models/Profile/Conditions/EventsCondition.cs new file mode 100644 index 000000000..8f8af3ad9 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/Conditions/EventsCondition.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Artemis.Storage.Entities.Profile.Abstract; +using Artemis.Storage.Entities.Profile.Conditions; + +namespace Artemis.Core +{ + public class EventsCondition : CorePropertyChanged, INodeScriptCondition + { + private readonly EventsConditionEntity _entity; + private readonly List _eventsList; + private TimeLineEventOverlapMode _eventOverlapMode; + + public EventsCondition(ProfileElement profileElement) + { + _entity = new EventsConditionEntity(); + _eventsList = new List(); + + ProfileElement = profileElement; + Events = new List(_eventsList); + } + + internal EventsCondition(EventsConditionEntity entity, ProfileElement profileElement) + { + _entity = entity; + _eventsList = new List(); + + ProfileElement = profileElement; + Events = new List(_eventsList); + + Load(); + } + + /// + /// Gets a list of events this condition reacts to + /// + public IReadOnlyCollection Events { get; } + + /// + /// Gets or sets how the condition behaves when events trigger before the timeline finishes + /// + public TimeLineEventOverlapMode EventOverlapMode + { + get => _eventOverlapMode; + set => SetAndNotify(ref _eventOverlapMode, value); + } + + /// + public IConditionEntity Entity => _entity; + + /// + public ProfileElement ProfileElement { get; } + + /// + public bool IsMet { get; private set; } + + /// + /// Adds a new event condition + /// + /// The newly created event condition + public EventCondition AddEventCondition() + { + EventCondition eventCondition = new(ProfileElement.GetType().Name.ToLower(), ProfileElement.Profile); + lock (_eventsList) + { + _eventsList.Add(eventCondition); + } + + return eventCondition; + } + + /// + /// Removes the provided event condition + /// + /// The event condition to remove + public void RemoveEventCondition(EventCondition eventCondition) + { + lock (_eventsList) + { + _eventsList.Remove(eventCondition); + } + } + + /// + public void Update() + { + lock (_eventsList) + { + if (EventOverlapMode == TimeLineEventOverlapMode.Toggle) + { + if (_eventsList.Any(c => c.Evaluate())) + IsMet = !IsMet; + } + else + { + IsMet = _eventsList.Any(c => c.Evaluate()); + } + } + } + + /// + public void ApplyToTimeline(bool isMet, bool wasMet, Timeline timeline) + { + if (!isMet) + return; + + // Event overlap mode doesn't apply in this case + if (timeline.IsFinished) + { + timeline.JumpToStart(); + return; + } + + // If the timeline was already running, look at the event overlap mode + if (EventOverlapMode == TimeLineEventOverlapMode.Restart) + timeline.JumpToStart(); + else if (EventOverlapMode == TimeLineEventOverlapMode.Copy) + timeline.AddExtraTimeline(); + else if (EventOverlapMode == TimeLineEventOverlapMode.Toggle && !wasMet) + timeline.JumpToStart(); + + // The remaining overlap mode is 'ignore' which requires no further action + } + + /// + public void Dispose() + { + foreach (EventCondition eventCondition in Events) + eventCondition.Dispose(); + } + + #region Storage + + /// + public void Load() + { + EventOverlapMode = (TimeLineEventOverlapMode) _entity.EventOverlapMode; + lock (_eventsList) + { + _eventsList.Clear(); + foreach (EventConditionEntity eventCondition in _entity.Events) + _eventsList.Add(new EventCondition(eventCondition, ProfileElement.GetType().Name.ToLower(), ProfileElement.Profile)); + } + } + + /// + public void Save() + { + _entity.EventOverlapMode = (int) EventOverlapMode; + _entity.Events.Clear(); + lock (_eventsList) + { + foreach (EventCondition eventCondition in _eventsList) + { + eventCondition.Save(); + _entity.Events.Add(eventCondition.Entity); + } + } + } + + /// + public void LoadNodeScript() + { + lock (_eventsList) + { + foreach (EventCondition eventCondition in _eventsList) + eventCondition.LoadNodeScript(); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/ICondition.cs b/src/Artemis.Core/Models/Profile/Conditions/ICondition.cs new file mode 100644 index 000000000..e62d21617 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/Conditions/ICondition.cs @@ -0,0 +1,51 @@ +using System; +using Artemis.Storage.Entities.Profile.Abstract; + +namespace Artemis.Core +{ + /// + /// Represents a condition applied to a + /// + public interface ICondition : IDisposable, IStorageModel + { + /// + /// Gets the entity used to store this condition + /// + public IConditionEntity Entity { get; } + + /// + /// Gets the profile element this condition applies to + /// + public ProfileElement ProfileElement { get; } + + /// + /// Gets a boolean indicating whether the condition is currently met + /// + + bool IsMet { get; } + + /// + /// Updates the condition + /// + void Update(); + + /// + /// Applies the display condition to the provided timeline + /// + /// + /// + /// The timeline to apply the display condition to + void ApplyToTimeline(bool isMet, bool wasMet, Timeline timeline); + } + + /// + /// Represents a condition applied to a using a + /// + public interface INodeScriptCondition : ICondition + { + /// + /// Loads the node script this node script condition uses + /// + void LoadNodeScript(); + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/StaticCondition.cs b/src/Artemis.Core/Models/Profile/Conditions/StaticCondition.cs new file mode 100644 index 000000000..f0a8d9e0b --- /dev/null +++ b/src/Artemis.Core/Models/Profile/Conditions/StaticCondition.cs @@ -0,0 +1,101 @@ +using System; +using System.Linq; +using Artemis.Storage.Entities.Profile.Abstract; +using Artemis.Storage.Entities.Profile.Conditions; + +namespace Artemis.Core +{ + public class StaticCondition : CorePropertyChanged, INodeScriptCondition + { + private readonly StaticConditionEntity _entity; + + public StaticCondition(ProfileElement profileElement) + { + _entity = new StaticConditionEntity(); + + ProfileElement = profileElement; + string typeDisplayName = profileElement.GetType().Name.ToLower(); + Script = new NodeScript($"Activate {typeDisplayName}", $"Whether or not this {typeDisplayName} should be active", profileElement.Profile); + } + + internal StaticCondition(StaticConditionEntity entity, ProfileElement profileElement) + { + _entity = entity; + + ProfileElement = profileElement; + Script = null!; + + Load(); + } + + /// + /// Gets the script that drives the static condition + /// + public NodeScript Script { get; private set; } + + /// + public IConditionEntity Entity => _entity; + + /// + public ProfileElement ProfileElement { get; } + + /// + public bool IsMet { get; private set; } + + /// + public void Update() + { + if (!Script.HasNodes) + { + IsMet = true; + return; + } + + Script.Run(); + IsMet = Script.Result; + } + + /// + public void ApplyToTimeline(bool isMet, bool wasMet, Timeline timeline) + { + if (isMet && !wasMet && timeline.IsFinished) + timeline.JumpToStart(); + else if (!isMet && wasMet && timeline.StopMode == TimelineStopMode.SkipToEnd) + timeline.JumpToEndSegment(); + } + + #region IDisposable + + /// + public void Dispose() + { + Script.Dispose(); + } + + #endregion + + #region Storage + + /// + public void Load() + { + string typeDisplayName = ProfileElement.GetType().Name.ToLower(); + Script = new NodeScript($"Activate {typeDisplayName}", $"Whether or not this {typeDisplayName} should be active", _entity.Script, ProfileElement.Profile); + } + + /// + public void Save() + { + Script.Save(); + _entity.Script = Script.Entity; + } + + /// + public void LoadNodeScript() + { + Script.Load(); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs index 37f5d8f8c..dbe3f785c 100644 --- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs @@ -18,17 +18,13 @@ namespace Artemis.Core { private SKRectI _bounds; private SKPath? _path; - private readonly string _typeDisplayName; internal RenderProfileElement(Profile profile) : base(profile) { - _typeDisplayName = this is Layer ? "layer" : "folder"; - _displayCondition = new NodeScript($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active", Profile); - Timeline = new Timeline(); ExpandedPropertyGroups = new List(); LayerEffectsList = new List(); - LayerEffects = new(LayerEffectsList); + LayerEffects = new ReadOnlyCollection(LayerEffectsList); LayerEffectStore.LayerEffectAdded += LayerEffectStoreOnLayerEffectAdded; LayerEffectStore.LayerEffectRemoved += LayerEffectStoreOnLayerEffectRemoved; @@ -64,7 +60,8 @@ namespace Artemis.Core foreach (BaseLayerEffect baseLayerEffect in LayerEffects) baseLayerEffect.Dispose(); - DisplayCondition.Dispose(); + if (DisplayCondition is IDisposable disposable) + disposable.Dispose(); base.Dispose(disposing); } @@ -97,11 +94,9 @@ namespace Artemis.Core layerEffect.BaseProperties?.ApplyToEntity(); } - // Conditions + // Condition DisplayCondition?.Save(); - RenderElementEntity.NodeScript = DisplayCondition?.Entity; - // RenderElementEntity.DisplayCondition = DisplayCondition?.Entity; - // DisplayCondition?.Save(); + RenderElementEntity.DisplayCondition = DisplayCondition?.Entity; // Timeline RenderElementEntity.Timeline = Timeline?.Entity; @@ -110,11 +105,10 @@ namespace Artemis.Core internal void LoadNodeScript() { - DisplayCondition = RenderElementEntity.NodeScript != null - ? new NodeScript($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active", RenderElementEntity.NodeScript, Profile) - : new NodeScript($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active", Profile); + if (DisplayCondition is INodeScriptCondition scriptCondition) + scriptCondition.LoadNodeScript(); - foreach (ILayerProperty layerProperty in GetAllLayerProperties()) + foreach (ILayerProperty layerProperty in GetAllLayerProperties()) layerProperty.BaseDataBinding.LoadNodeScript(); } @@ -367,20 +361,19 @@ namespace Artemis.Core protected set => SetAndNotify(ref _displayConditionMet, value); } - private NodeScript _displayCondition; private bool _displayConditionMet; - private bool _toggledOnByEvent = false; - /// - /// Gets or sets the root display condition group + /// Gets the display condition used to determine whether this element is active or not /// - public NodeScript DisplayCondition + public ICondition? DisplayCondition { get => _displayCondition; - set => SetAndNotify(ref _displayCondition, value); + private set => SetAndNotify(ref _displayCondition, value); } + private ICondition? _displayCondition; + /// /// Evaluates the display conditions on this element and applies any required changes to the /// @@ -392,63 +385,30 @@ namespace Artemis.Core return; } - if (!DisplayCondition.HasNodes) + if (DisplayCondition == null) { DisplayConditionMet = true; return; } - if (Timeline.EventOverlapMode != TimeLineEventOverlapMode.Toggle) - _toggledOnByEvent = false; + DisplayCondition.Update(); + DisplayCondition.ApplyToTimeline(DisplayCondition.IsMet, DisplayConditionMet, Timeline); + DisplayConditionMet = DisplayCondition.IsMet; + } - DisplayCondition.Run(); + /// + /// Replaces the current with the provided or + /// + /// + /// The condition to change the to + public void ChangeDisplayCondition(ICondition? condition) + { + if (condition == DisplayCondition) + return; - // TODO: Handle this nicely, right now when there's only an exit node we assume true - bool conditionMet = DisplayCondition.Nodes.Count() == 1 || DisplayCondition.Result; - if (Parent is RenderProfileElement parent && !parent.DisplayConditionMet) - conditionMet = false; - - // if (!DisplayCondition.ContainsEvents) - { - // Regular conditions reset the timeline whenever their condition is met and was not met before that - if (conditionMet && !DisplayConditionMet && Timeline.IsFinished) - Timeline.JumpToStart(); - // If regular conditions are no longer met, jump to the end segment if stop mode requires it - if (!conditionMet && Timeline.StopMode == TimelineStopMode.SkipToEnd) - Timeline.JumpToEndSegment(); - } - // else if (conditionMet) - // { - // if (Timeline.EventOverlapMode == TimeLineEventOverlapMode.Toggle) - // { - // _toggledOnByEvent = !_toggledOnByEvent; - // if (_toggledOnByEvent) - // Timeline.JumpToStart(); - // } - // else - // { - // // Event conditions reset if the timeline finished - // if (Timeline.IsFinished) - // { - // Timeline.JumpToStart(); - // } - // // and otherwise apply their overlap mode - // else - // { - // if (Timeline.EventOverlapMode == TimeLineEventOverlapMode.Restart) - // Timeline.JumpToStart(); - // else if (Timeline.EventOverlapMode == TimeLineEventOverlapMode.Copy) - // Timeline.AddExtraTimeline(); - // // The third option is ignore which is handled below: - // - // // done - // } - // } - // } - - DisplayConditionMet = Timeline.EventOverlapMode == TimeLineEventOverlapMode.Toggle - ? _toggledOnByEvent - : conditionMet; + ICondition? old = DisplayCondition; + DisplayCondition = condition; + old?.Dispose(); } #endregion diff --git a/src/Artemis.Storage/Entities/Profile/Abstract/RenderElementEntity.cs b/src/Artemis.Storage/Entities/Profile/Abstract/RenderElementEntity.cs index 8dc4c6044..26f1d9c53 100644 --- a/src/Artemis.Storage/Entities/Profile/Abstract/RenderElementEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/Abstract/RenderElementEntity.cs @@ -14,7 +14,7 @@ namespace Artemis.Storage.Entities.Profile.Abstract public List PropertyEntities { get; set; } public List ExpandedPropertyGroups { get; set; } - public DataModelConditionGroupEntity DisplayCondition { get; set; } + public IConditionEntity DisplayCondition { get; set; } public TimelineEntity Timeline { get; set; } public NodeScriptEntity NodeScript { get; set; } diff --git a/src/Artemis.Storage/Entities/Profile/AdaptionHints/CategoryAdaptionHintEntity.cs b/src/Artemis.Storage/Entities/Profile/AdaptionHints/CategoryAdaptionHintEntity.cs index bd5fb6f08..64dcc669a 100644 --- a/src/Artemis.Storage/Entities/Profile/AdaptionHints/CategoryAdaptionHintEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/AdaptionHints/CategoryAdaptionHintEntity.cs @@ -8,6 +8,4 @@ public int Skip { get; set; } public int Amount { get; set; } } - - } \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionEventEntity.cs b/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionEventEntity.cs deleted file mode 100644 index 141f5bb08..000000000 --- a/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionEventEntity.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; -using Artemis.Storage.Entities.Profile.Abstract; - -namespace Artemis.Storage.Entities.Profile.Conditions -{ - public class DataModelConditionEventEntity : DataModelConditionPartEntity - { - public DataModelConditionEventEntity() - { - Children = new List(); - } - - public DataModelPathEntity EventPath { get; set; } - } -} \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionEventPredicateEntity.cs b/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionEventPredicateEntity.cs deleted file mode 100644 index fee134489..000000000 --- a/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionEventPredicateEntity.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Artemis.Storage.Entities.Profile.Conditions -{ - public class DataModelConditionEventPredicateEntity : DataModelConditionPredicateEntity - { - } -} \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionGeneralPredicateEntity.cs b/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionGeneralPredicateEntity.cs deleted file mode 100644 index 1a0fa93ea..000000000 --- a/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionGeneralPredicateEntity.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Artemis.Storage.Entities.Profile.Conditions -{ - public class DataModelConditionGeneralPredicateEntity : DataModelConditionPredicateEntity - { - } -} \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionGroupEntity.cs b/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionGroupEntity.cs deleted file mode 100644 index 90f84494e..000000000 --- a/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionGroupEntity.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; -using Artemis.Storage.Entities.Profile.Abstract; - -namespace Artemis.Storage.Entities.Profile.Conditions -{ - public class DataModelConditionGroupEntity : DataModelConditionPartEntity - { - public DataModelConditionGroupEntity() - { - Children = new List(); - } - - public int BooleanOperator { get; set; } - } -} \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionListEntity.cs b/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionListEntity.cs deleted file mode 100644 index fd812cc41..000000000 --- a/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionListEntity.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Collections.Generic; -using Artemis.Storage.Entities.Profile.Abstract; - -namespace Artemis.Storage.Entities.Profile.Conditions -{ - public class DataModelConditionListEntity : DataModelConditionPartEntity - { - public DataModelConditionListEntity() - { - Children = new List(); - } - - public DataModelPathEntity ListPath { get; set; } - public int ListOperator { get; set; } - } -} \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionListPredicateEntity.cs b/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionListPredicateEntity.cs deleted file mode 100644 index cfc4b5372..000000000 --- a/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionListPredicateEntity.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Artemis.Storage.Entities.Profile.Conditions -{ - public class DataModelConditionListPredicateEntity : DataModelConditionPredicateEntity - { - } -} \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionPredicateEntity.cs b/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionPredicateEntity.cs deleted file mode 100644 index c913a6a3c..000000000 --- a/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionPredicateEntity.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using Artemis.Storage.Entities.Profile.Abstract; - -namespace Artemis.Storage.Entities.Profile.Conditions -{ - public abstract class DataModelConditionPredicateEntity : DataModelConditionPartEntity - { - public int PredicateType { get; set; } - public DataModelPathEntity LeftPath { get; set; } - public DataModelPathEntity RightPath { get; set; } - - public string OperatorType { get; set; } - public Guid? OperatorPluginGuid { get; set; } - - // Stored as a string to be able to control serialization and deserialization ourselves - public string RightStaticValue { get; set; } - } -} \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/Conditions/EventsConditionEntity.cs b/src/Artemis.Storage/Entities/Profile/Conditions/EventsConditionEntity.cs new file mode 100644 index 000000000..05404882d --- /dev/null +++ b/src/Artemis.Storage/Entities/Profile/Conditions/EventsConditionEntity.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using Artemis.Storage.Entities.Profile.Abstract; +using Artemis.Storage.Entities.Profile.Nodes; + +namespace Artemis.Storage.Entities.Profile.Conditions +{ + public class EventsConditionEntity : IConditionEntity + { + public int EventOverlapMode { get; set; } + public List Events { get; set; } = new(); + } + + public class EventConditionEntity + { + public DataModelPathEntity EventPath { get; set; } + public NodeScriptEntity Script { get; set; } + } +} \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/Conditions/IConditionEntity.cs b/src/Artemis.Storage/Entities/Profile/Conditions/IConditionEntity.cs new file mode 100644 index 000000000..34d5ca589 --- /dev/null +++ b/src/Artemis.Storage/Entities/Profile/Conditions/IConditionEntity.cs @@ -0,0 +1,6 @@ +namespace Artemis.Storage.Entities.Profile.Abstract +{ + public interface IConditionEntity + { + } +} \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/Conditions/StaticConditionEntity.cs b/src/Artemis.Storage/Entities/Profile/Conditions/StaticConditionEntity.cs new file mode 100644 index 000000000..38a938c9a --- /dev/null +++ b/src/Artemis.Storage/Entities/Profile/Conditions/StaticConditionEntity.cs @@ -0,0 +1,10 @@ +using Artemis.Storage.Entities.Profile.Abstract; +using Artemis.Storage.Entities.Profile.Nodes; + +namespace Artemis.Storage.Entities.Profile.Conditions +{ + public class StaticConditionEntity : IConditionEntity + { + public NodeScriptEntity Script { get; set; } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs index 640d283cd..4936bf68d 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs @@ -121,7 +121,7 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions if (RenderProfileElement == null) return; - _windowManager.ShowDialog(_nodeVmFactory.NodeScriptWindowViewModel(RenderProfileElement.DisplayCondition)); + // _windowManager.ShowDialog(_nodeVmFactory.NodeScriptWindowViewModel(RenderProfileElement.DisplayCondition)); _profileEditorService.SaveSelectedProfileElement(); } From d657e7844ed1a40202a4d118feae65a4daa6bb69 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 14 Sep 2021 22:17:31 +0200 Subject: [PATCH 35/47] Profiles - Split conditions into different types Profile configurations - Added broken state (not yet shown in UI) --- .../Profile/Conditions/EventCondition.cs | 35 ++- .../Profile/Conditions/EventsCondition.cs | 5 +- .../ProfileConfiguration.cs | 9 +- .../Services/Storage/ProfileService.cs | 8 +- .../VisualScripting/Interfaces/INode.cs | 1 + .../Internal/EventStartNode.cs | 49 ++++ src/Artemis.Core/VisualScripting/Node.cs | 2 + .../Ninject/Factories/IVMFactory.cs | 9 + .../DisplayConditionEventViewModel.cs | 13 - .../DisplayConditionsView.xaml | 224 ++---------------- .../DisplayConditionsViewModel.cs | 159 +++++-------- .../Event/EventConditionView.xaml | 81 +++++++ .../Event/EventConditionViewModel.cs | 83 +++++++ .../EventsConditionView.xaml} | 83 +++---- .../Event/EventsConditionViewModel.cs | 65 +++++ .../Static/StaticConditionView.xaml | 181 ++++++++++++++ .../Static/StaticConditionViewModel.cs | 60 +++++ .../Controls/Wrapper/VisualScriptNode.cs | 2 +- 18 files changed, 694 insertions(+), 375 deletions(-) create mode 100644 src/Artemis.Core/VisualScripting/Internal/EventStartNode.cs delete mode 100644 src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionEventViewModel.cs create mode 100644 src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Event/EventConditionView.xaml create mode 100644 src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Event/EventConditionViewModel.cs rename src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/{DisplayConditionEventView.xaml => Event/EventsConditionView.xaml} (68%) create mode 100644 src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Event/EventsConditionViewModel.cs create mode 100644 src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Static/StaticConditionView.xaml create mode 100644 src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Static/StaticConditionViewModel.cs diff --git a/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs b/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs index ad7913b54..9b51e0fcc 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs @@ -1,4 +1,6 @@ using System; +using System.Linq; +using Artemis.Core.Internal; using Artemis.Storage.Entities.Profile.Conditions; namespace Artemis.Core @@ -8,7 +10,7 @@ namespace Artemis.Core private readonly string _displayName; private readonly object? _context; private DateTime _lastProcessedTrigger; - private DataModelPath _eventPath; + private DataModelPath? _eventPath; internal EventCondition(string displayName, object? context) { @@ -17,6 +19,7 @@ namespace Artemis.Core Entity = new EventConditionEntity(); Script = new NodeScript($"Activate {displayName}", $"Whether or not the event should activate the {displayName}", context); + UpdateEventNode(); } internal EventCondition(EventConditionEntity entity, string displayName, object? context) @@ -38,7 +41,7 @@ namespace Artemis.Core /// /// Gets or sets the path to the event that drives this event condition /// - public DataModelPath EventPath + public DataModelPath? EventPath { set => SetAndNotify(ref _eventPath, value); get => _eventPath; @@ -48,7 +51,7 @@ namespace Artemis.Core internal bool Evaluate() { - if (EventPath.GetValue() is not DataModelEvent dataModelEvent || dataModelEvent.LastTrigger <= _lastProcessedTrigger) + if (EventPath?.GetValue() is not IDataModelEvent dataModelEvent || dataModelEvent.LastTrigger <= _lastProcessedTrigger) return false; // TODO: Place dataModelEvent.LastEventArgumentsUntyped; in the start node @@ -65,7 +68,7 @@ namespace Artemis.Core public void Dispose() { Script.Dispose(); - EventPath.Dispose(); + EventPath?.Dispose(); } #endregion @@ -73,6 +76,25 @@ namespace Artemis.Core internal void LoadNodeScript() { Script.Load(); + UpdateEventNode(); + } + + /// + /// Updates the event node, applying the selected event + /// + public void UpdateEventNode() + { + if (EventPath?.GetValue() is not IDataModelEvent dataModelEvent) + return; + + if (Script.Nodes.FirstOrDefault(n => n is EventStartNode) is EventStartNode existing) + existing.UpdateDataModelEvent(dataModelEvent); + else + { + EventStartNode node = new(); + node.UpdateDataModelEvent(dataModelEvent); + Script.AddNode(node); + } } #region Implementation of IStorageModel @@ -82,13 +104,14 @@ namespace Artemis.Core { EventPath = new DataModelPath(null, Entity.EventPath); Script = new NodeScript($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", Entity.Script, _context); + UpdateEventNode(); } /// public void Save() { - EventPath.Save(); - Entity.EventPath = EventPath.Entity; + EventPath?.Save(); + Entity.EventPath = EventPath?.Entity; Script.Save(); Entity.Script = Script.Entity; } diff --git a/src/Artemis.Core/Models/Profile/Conditions/EventsCondition.cs b/src/Artemis.Core/Models/Profile/Conditions/EventsCondition.cs index 8f8af3ad9..86edae3e0 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/EventsCondition.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/EventsCondition.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using Artemis.Storage.Entities.Profile.Abstract; using Artemis.Storage.Entities.Profile.Conditions; @@ -18,7 +19,7 @@ namespace Artemis.Core _eventsList = new List(); ProfileElement = profileElement; - Events = new List(_eventsList); + Events = new ReadOnlyCollection(_eventsList); } internal EventsCondition(EventsConditionEntity entity, ProfileElement profileElement) @@ -27,7 +28,7 @@ namespace Artemis.Core _eventsList = new List(); ProfileElement = profileElement; - Events = new List(_eventsList); + Events = new ReadOnlyCollection(_eventsList); Load(); } diff --git a/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs b/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs index b2ce08edb..20ba2ddc3 100644 --- a/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs +++ b/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs @@ -9,7 +9,7 @@ namespace Artemis.Core /// /// Represents the configuration of a profile, contained in a /// - public class ProfileConfiguration : CorePropertyChanged, IStorageModel, IDisposable + public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable { private ProfileCategory _category; private bool _disposed; @@ -277,6 +277,13 @@ namespace Artemis.Core } #endregion + + #region Overrides of BreakableModel + + /// + public override string BrokenDisplayName => "Profile Configuration"; + + #endregion } /// diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index 68db68316..d981c27ff 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -221,8 +221,8 @@ namespace Artemis.Core.Services try { // Make sure the profile is active or inactive according to the parameters above - if (shouldBeActive && profileConfiguration.Profile == null) - ActivateProfile(profileConfiguration); + if (shouldBeActive && profileConfiguration.Profile == null && profileConfiguration.BrokenState != "Failed to activate profile") + profileConfiguration.TryOrBreak(() => ActivateProfile(profileConfiguration), "Failed to activate profile"); else if (!shouldBeActive && profileConfiguration.Profile != null) DeactivateProfile(profileConfiguration); @@ -597,9 +597,9 @@ namespace Artemis.Core.Services profile.Save(); - foreach (RenderProfileElement renderProfileElement in profile.GetAllRenderElements()) + foreach (RenderProfileElement renderProfileElement in profile.GetAllRenderElements()) renderProfileElement.Save(); - + _logger.Debug("Adapt profile - Saving " + profile); profile.RedoStack.Clear(); profile.UndoStack.Push(memento); diff --git a/src/Artemis.Core/VisualScripting/Interfaces/INode.cs b/src/Artemis.Core/VisualScripting/Interfaces/INode.cs index 915f5bac1..a5d734f3d 100644 --- a/src/Artemis.Core/VisualScripting/Interfaces/INode.cs +++ b/src/Artemis.Core/VisualScripting/Interfaces/INode.cs @@ -10,6 +10,7 @@ namespace Artemis.Core string Name { get; } string Description { get; } bool IsExitNode { get; } + bool IsDefaultNode { get; } public double X { get; set; } public double Y { get; set; } diff --git a/src/Artemis.Core/VisualScripting/Internal/EventStartNode.cs b/src/Artemis.Core/VisualScripting/Internal/EventStartNode.cs new file mode 100644 index 000000000..c6fc57242 --- /dev/null +++ b/src/Artemis.Core/VisualScripting/Internal/EventStartNode.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Humanizer; + +namespace Artemis.Core.Internal +{ + internal class EventStartNode : Node + { + private IDataModelEvent? _dataModelEvent; + private readonly Dictionary _propertyPins; + + public EventStartNode() : base("Event Arguments", "Contains the event arguments that triggered the evaluation") + { + _propertyPins = new Dictionary(); + } + + public void UpdateDataModelEvent(IDataModelEvent dataModelEvent) + { + if (_dataModelEvent == dataModelEvent) + return; + + foreach (var (_, outputPin) in _propertyPins) + RemovePin(outputPin); + _propertyPins.Clear(); + + _dataModelEvent = dataModelEvent; + foreach (PropertyInfo propertyInfo in dataModelEvent.ArgumentsType.GetProperties(BindingFlags.Instance | BindingFlags.Public)) + _propertyPins.Add(propertyInfo, CreateOutputPin(propertyInfo.PropertyType, propertyInfo.Name.Humanize())); + } + + #region Overrides of Node + + /// + public override void Evaluate() + { + if (_dataModelEvent?.LastEventArgumentsUntyped == null) + return; + + foreach (var (propertyInfo, outputPin) in _propertyPins) + { + if (outputPin.ConnectedTo.Any()) + outputPin.Value = propertyInfo.GetValue(_dataModelEvent.LastEventArgumentsUntyped) ?? outputPin.Type.GetDefault()!; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.Core/VisualScripting/Node.cs b/src/Artemis.Core/VisualScripting/Node.cs index 4d1acc1eb..669de437f 100644 --- a/src/Artemis.Core/VisualScripting/Node.cs +++ b/src/Artemis.Core/VisualScripting/Node.cs @@ -25,6 +25,7 @@ namespace Artemis.Core } private double _x; + public double X { get => _x; @@ -46,6 +47,7 @@ namespace Artemis.Core } public virtual bool IsExitNode => false; + public virtual bool IsDefaultNode => false; private readonly List _pins = new(); public IReadOnlyCollection Pins => new ReadOnlyCollection(_pins); diff --git a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs index aaf677e09..bc46e929a 100644 --- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -3,6 +3,8 @@ using Artemis.Core.Modules; using Artemis.Core.ScriptingProviders; using Artemis.UI.Screens.Header; using Artemis.UI.Screens.Plugins; +using Artemis.UI.Screens.ProfileEditor.DisplayConditions.Event; +using Artemis.UI.Screens.ProfileEditor.DisplayConditions.Static; using Artemis.UI.Screens.ProfileEditor.LayerProperties; using Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings; using Artemis.UI.Screens.ProfileEditor.LayerProperties.LayerEffects; @@ -92,6 +94,13 @@ namespace Artemis.UI.Ninject.Factories TimelineSegmentViewModel TimelineSegmentViewModel(SegmentViewModelType segment, IObservableCollection layerPropertyGroups); } + public interface IConditionVmFactory : IVmFactory + { + StaticConditionViewModel StaticConditionViewModel(StaticCondition staticCondition); + EventsConditionViewModel EventsConditionViewModel(EventsCondition eventsCondition); + EventConditionViewModel EventConditionViewModel(EventCondition eventCondition); + } + public interface IPrerequisitesVmFactory : IVmFactory { PluginPrerequisiteViewModel PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, bool uninstall); diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionEventViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionEventViewModel.cs deleted file mode 100644 index 0c0a97b2c..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionEventViewModel.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Stylet; - -namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions -{ - public class DisplayConditionEventViewModel : Screen - { - } -} diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml index abaa2d145..9d5962688 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml @@ -37,10 +37,28 @@ + + + + + No condition, the element is always displayed + + + + + + NONE + + + @@ -55,9 +73,9 @@ CONSTANT - @@ -78,200 +96,10 @@ - - - - - - - - - - - - - - - - - - - - - - Click to edit script - - - - - - - - - - - - - - - - - - - - - Configure how the layer should act while the conditions above are met - - - - - - - - - - - - - - - Continue repeating the main segment of the timeline while the condition is met - - - - - - REPEAT - - - - - - - Only play the timeline once when the condition is met - - - - - - ONCE - - - - - - - - - - - Configure how the layer should act when the conditions above are no longer met - - - - - - - - - - - - - - - When conditions are no longer met, finish the the current run of the main timeline - - - - - - FINISH - - - - - - - When conditions are no longer met, skip to the end segment of the timeline - - - - - - SKIP TO END - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs index 4936bf68d..699ea3340 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs @@ -1,6 +1,4 @@ -using System.ComponentModel; -using System.Windows.Input; -using Artemis.Core; +using Artemis.Core; using Artemis.UI.Ninject.Factories; using Artemis.UI.Shared; using Artemis.UI.Shared.Services; @@ -8,84 +6,31 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions { - public class DisplayConditionsViewModel : Conductor.Collection.OneActive, IProfileEditorPanelViewModel + public class DisplayConditionsViewModel : Conductor, IProfileEditorPanelViewModel { - private readonly INodeVmFactory _nodeVmFactory; + private readonly IConditionVmFactory _conditionVmFactory; private readonly IProfileEditorService _profileEditorService; - private readonly IWindowManager _windowManager; - private bool _isEventCondition; - private RenderProfileElement _renderProfileElement; + private DisplayConditionType _displayConditionType; - public DisplayConditionsViewModel(IProfileEditorService profileEditorService, IWindowManager windowManager, INodeVmFactory nodeVmFactory) + public DisplayConditionsViewModel(IProfileEditorService profileEditorService, IConditionVmFactory conditionVmFactory) { _profileEditorService = profileEditorService; - _windowManager = windowManager; - _nodeVmFactory = nodeVmFactory; - - Items.Add(new DisplayConditionEventViewModel() { DisplayName = "Event 1"}); - Items.Add(new DisplayConditionEventViewModel() { DisplayName = "Event 2"}); - Items.Add(new DisplayConditionEventViewModel() { DisplayName = "Event 3"}); - Items.Add(new DisplayConditionEventViewModel() { DisplayName = "Event 4"}); + _conditionVmFactory = conditionVmFactory; } - public bool IsEventCondition + public DisplayConditionType DisplayConditionType { - get => _isEventCondition; - set => SetAndNotify(ref _isEventCondition, value); - } - - public RenderProfileElement RenderProfileElement - { - get => _renderProfileElement; + get => _displayConditionType; set { - if (!SetAndNotify(ref _renderProfileElement, value)) return; - NotifyOfPropertyChange(nameof(DisplayContinuously)); - NotifyOfPropertyChange(nameof(AlwaysFinishTimeline)); - NotifyOfPropertyChange(nameof(EventOverlapMode)); + if (!SetAndNotify(ref _displayConditionType, value)) return; + ChangeConditionType(); } } - public bool DisplayContinuously - { - get => RenderProfileElement?.Timeline.PlayMode == TimelinePlayMode.Repeat; - set - { - TimelinePlayMode playMode = value ? TimelinePlayMode.Repeat : TimelinePlayMode.Once; - if (RenderProfileElement == null || RenderProfileElement?.Timeline.PlayMode == playMode) return; - RenderProfileElement.Timeline.PlayMode = playMode; - _profileEditorService.SaveSelectedProfileElement(); - } - } - - public bool AlwaysFinishTimeline - { - get => RenderProfileElement?.Timeline.StopMode == TimelineStopMode.Finish; - set - { - TimelineStopMode stopMode = value ? TimelineStopMode.Finish : TimelineStopMode.SkipToEnd; - if (RenderProfileElement == null || RenderProfileElement?.Timeline.StopMode == stopMode) return; - RenderProfileElement.Timeline.StopMode = stopMode; - _profileEditorService.SaveSelectedProfileElement(); - } - } - - public TimeLineEventOverlapMode EventOverlapMode - { - get => RenderProfileElement?.Timeline.EventOverlapMode ?? TimeLineEventOverlapMode.Restart; - set - { - if (RenderProfileElement == null || RenderProfileElement?.Timeline.EventOverlapMode == value) return; - RenderProfileElement.Timeline.EventOverlapMode = value; - _profileEditorService.SaveSelectedProfileElement(); - } - } - - public bool ConditionBehaviourEnabled => RenderProfileElement != null; - protected override void OnInitialActivate() { - _profileEditorService.SelectedProfileElementChanged += SelectedProfileEditorServiceOnSelectedProfileElementChanged; + _profileEditorService.SelectedProfileElementChanged += ProfileEditorServiceOnSelectedProfileElementChanged; Update(_profileEditorService.SelectedProfileElement); base.OnInitialActivate(); @@ -93,55 +38,63 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions protected override void OnClose() { - _profileEditorService.SelectedProfileElementChanged -= SelectedProfileEditorServiceOnSelectedProfileElementChanged; + _profileEditorService.SelectedProfileElementChanged -= ProfileEditorServiceOnSelectedProfileElementChanged; base.OnClose(); } + private void ChangeConditionType() + { + if (_profileEditorService.SelectedProfileElement == null) + return; + + if (DisplayConditionType == DisplayConditionType.Static) + _profileEditorService.SelectedProfileElement.ChangeDisplayCondition(new StaticCondition(_profileEditorService.SelectedProfileElement)); + else if (DisplayConditionType == DisplayConditionType.Events) + _profileEditorService.SelectedProfileElement.ChangeDisplayCondition(new EventsCondition(_profileEditorService.SelectedProfileElement)); + else + _profileEditorService.SelectedProfileElement.ChangeDisplayCondition(null); + + _profileEditorService.SaveSelectedProfileElement(); + Update(_profileEditorService.SelectedProfileElement); + } + private void Update(RenderProfileElement renderProfileElement) { - if (RenderProfileElement != null) - RenderProfileElement.Timeline.PropertyChanged -= TimelineOnPropertyChanged; - - RenderProfileElement = renderProfileElement; - - NotifyOfPropertyChange(nameof(DisplayContinuously)); - NotifyOfPropertyChange(nameof(AlwaysFinishTimeline)); - NotifyOfPropertyChange(nameof(ConditionBehaviourEnabled)); - - if (RenderProfileElement != null) - RenderProfileElement.Timeline.PropertyChanged += TimelineOnPropertyChanged; - } - - #region Event handlers - - public void ScriptGridMouseUp(object sender, MouseButtonEventArgs e) - { - if (e.ChangedButton != MouseButton.Left) - return; - if (RenderProfileElement == null) + if (renderProfileElement == null) + { + ActiveItem = null; return; + } - // _windowManager.ShowDialog(_nodeVmFactory.NodeScriptWindowViewModel(RenderProfileElement.DisplayCondition)); - _profileEditorService.SaveSelectedProfileElement(); + if (renderProfileElement.DisplayCondition is StaticCondition staticCondition) + { + ActiveItem = _conditionVmFactory.StaticConditionViewModel(staticCondition); + _displayConditionType = DisplayConditionType.Static; + } + else if (renderProfileElement.DisplayCondition is EventsCondition eventsCondition) + { + ActiveItem = _conditionVmFactory.EventsConditionViewModel(eventsCondition); + _displayConditionType = DisplayConditionType.Events; + } + else + { + ActiveItem = null; + _displayConditionType = DisplayConditionType.None; + } + + NotifyOfPropertyChange(nameof(DisplayConditionType)); } - public void EventTriggerModeSelected() - { - _profileEditorService.SaveSelectedProfileElement(); - } - - private void SelectedProfileEditorServiceOnSelectedProfileElementChanged(object sender, RenderProfileElementEventArgs e) + private void ProfileEditorServiceOnSelectedProfileElementChanged(object sender, RenderProfileElementEventArgs e) { Update(e.RenderProfileElement); } + } - private void TimelineOnPropertyChanged(object sender, PropertyChangedEventArgs e) - { - NotifyOfPropertyChange(nameof(DisplayContinuously)); - NotifyOfPropertyChange(nameof(AlwaysFinishTimeline)); - NotifyOfPropertyChange(nameof(EventOverlapMode)); - } - - #endregion + public enum DisplayConditionType + { + None, + Static, + Events } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Event/EventConditionView.xaml b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Event/EventConditionView.xaml new file mode 100644 index 000000000..1ebd5faf6 --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Event/EventConditionView.xaml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + - + 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, )", From 02f4609eae19e60857c894d3af06c09f64cace7a Mon Sep 17 00:00:00 2001 From: Robert Date: Sat, 18 Sep 2021 13:38:09 +0200 Subject: [PATCH 38/47] Sidebar - Show message when profile is broken Events - Keep the old event type around for if you change your mind --- .../Models/Profile/RenderProfileElement.cs | 17 +----------- .../Services/Storage/ProfileService.cs | 12 ++++++++- .../DisplayConditionsView.xaml | 17 ++++++++++-- .../DisplayConditionsViewModel.cs | 20 +++++++++++--- .../Event/EventConditionView.xaml | 6 ++--- .../Event/EventConditionViewModel.cs | 1 + .../SidebarProfileConfigurationView.xaml | 26 +++++++++++++++++-- .../Screens/Sidebar/SidebarViewModel.cs | 6 +++++ 8 files changed, 78 insertions(+), 27 deletions(-) diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs index e33de9fc7..6428c82e4 100644 --- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs @@ -377,7 +377,7 @@ namespace Artemis.Core public ICondition? DisplayCondition { get => _displayCondition; - private set => SetAndNotify(ref _displayCondition, value); + set => SetAndNotify(ref _displayCondition, value); } private ICondition? _displayCondition; @@ -404,21 +404,6 @@ namespace Artemis.Core DisplayConditionMet = DisplayCondition.IsMet; } - /// - /// Replaces the current with the provided or - /// - /// - /// The condition to change the to - public void ChangeDisplayCondition(ICondition? condition) - { - if (condition == DisplayCondition) - return; - - ICondition? old = DisplayCondition; - DisplayCondition = condition; - old?.Dispose(); - } - #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index d981c27ff..13674ddfd 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -327,7 +327,17 @@ namespace Artemis.Core.Services if (profileConfiguration.Profile != null) return profileConfiguration.Profile; - ProfileEntity profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId); + ProfileEntity profileEntity; + try + { + profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId); + } + catch (Exception e) + { + profileConfiguration.SetBrokenState("Failed to activate profile", e); + throw; + } + if (profileEntity == null) throw new ArtemisCoreException($"Cannot find profile named: {profileConfiguration.Name} ID: {profileConfiguration.Entity.ProfileId}"); diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml index beb277709..36e087d64 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml @@ -15,7 +15,6 @@ d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance {x:Type displayConditions:DisplayConditionsViewModel}}"> - @@ -100,6 +99,20 @@ s:View.Model="{Binding ActiveItem}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" - IsTabStop="False"/> + IsTabStop="False" + Visibility="{Binding ActiveItem, Converter={StaticResource NullToVisibilityConverter}}"/> + + + + Display conditions disabled + + + + This element will always display. + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs index 134025bf3..56030d322 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs @@ -11,6 +11,8 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions private readonly IConditionVmFactory _conditionVmFactory; private readonly IProfileEditorService _profileEditorService; private DisplayConditionType _displayConditionType; + private StaticCondition _staticCondition; + private EventCondition _eventCondition; public DisplayConditionsViewModel(IProfileEditorService profileEditorService, IConditionVmFactory conditionVmFactory) { @@ -47,12 +49,19 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions if (_profileEditorService.SelectedProfileElement == null) return; + // Keep the old condition around in case the user changes their mind + if (_profileEditorService.SelectedProfileElement.DisplayCondition is StaticCondition staticCondition) + _staticCondition = staticCondition; + else if (_profileEditorService.SelectedProfileElement.DisplayCondition is EventCondition eventCondition) + _eventCondition = eventCondition; + + // If we have the old condition around put it back if (DisplayConditionType == DisplayConditionType.Static) - _profileEditorService.SelectedProfileElement.ChangeDisplayCondition(new StaticCondition(_profileEditorService.SelectedProfileElement)); + _profileEditorService.SelectedProfileElement.DisplayCondition = _staticCondition ?? new StaticCondition(_profileEditorService.SelectedProfileElement); else if (DisplayConditionType == DisplayConditionType.Events) - _profileEditorService.SelectedProfileElement.ChangeDisplayCondition(new EventCondition(_profileEditorService.SelectedProfileElement)); + _profileEditorService.SelectedProfileElement.DisplayCondition = _eventCondition ?? new EventCondition(_profileEditorService.SelectedProfileElement); else - _profileEditorService.SelectedProfileElement.ChangeDisplayCondition(null); + _profileEditorService.SelectedProfileElement.DisplayCondition = null; _profileEditorService.SaveSelectedProfileElement(); Update(_profileEditorService.SelectedProfileElement); @@ -87,6 +96,11 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions private void ProfileEditorServiceOnSelectedProfileElementChanged(object sender, RenderProfileElementEventArgs e) { + if (_staticCondition != null && e.PreviousRenderProfileElement?.DisplayCondition != _staticCondition) + _staticCondition.Dispose(); + if (_eventCondition != null && e.PreviousRenderProfileElement?.DisplayCondition != _eventCondition) + _eventCondition.Dispose(); + Update(e.RenderProfileElement); } } diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Event/EventConditionView.xaml b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Event/EventConditionView.xaml index ca4f75c29..90c7225da 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Event/EventConditionView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Event/EventConditionView.xaml @@ -50,7 +50,7 @@ IsChecked="{Binding TriggerConditionally}" Content="Conditional trigger" VerticalAlignment="Center" - ToolTip="When enabled, the layer will only trigger if the script evaluates to true" + ToolTip="When enabled, the element will only trigger if the script evaluates to true" Style="{StaticResource MaterialDesignDarkCheckBox}" /> - + @@ -105,7 +105,7 @@ - When enabled, the layer will only trigger if the script evaluates to true + This element will trigger any time the event fires. diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Event/EventConditionViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Event/EventConditionViewModel.cs index ec3219de3..bdb5eacfb 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Event/EventConditionViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Event/EventConditionViewModel.cs @@ -84,6 +84,7 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions.Event { EventCondition.UpdateEventNode(); DisplayName = EventCondition.EventPath?.Segments.LastOrDefault()?.GetPropertyDescription()?.Name ?? "Invalid event"; + _profileEditorService.SaveSelectedProfileElement(); } public void ScriptGridMouseUp(object sender, MouseButtonEventArgs e) diff --git a/src/Artemis.UI/Screens/Sidebar/SidebarProfileConfigurationView.xaml b/src/Artemis.UI/Screens/Sidebar/SidebarProfileConfigurationView.xaml index 3c54f19fc..4cd8e5f90 100644 --- a/src/Artemis.UI/Screens/Sidebar/SidebarProfileConfigurationView.xaml +++ b/src/Artemis.UI/Screens/Sidebar/SidebarProfileConfigurationView.xaml @@ -19,6 +19,7 @@ + @@ -183,7 +184,13 @@ -