From 32a444fbebd08b3c6e9a8fab31d7c37045e5dc75 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 16 Sep 2022 20:19:19 +0200 Subject: [PATCH 1/2] Auto-update - Fix URL, restoring auto-update functionality --- src/Artemis.UI.Windows/Providers/UpdateProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Artemis.UI.Windows/Providers/UpdateProvider.cs b/src/Artemis.UI.Windows/Providers/UpdateProvider.cs index fc02a0a6e..42df93619 100644 --- a/src/Artemis.UI.Windows/Providers/UpdateProvider.cs +++ b/src/Artemis.UI.Windows/Providers/UpdateProvider.cs @@ -45,7 +45,7 @@ public class UpdateProvider : IUpdateProvider, IDisposable Url request = API_URL.AppendPathSegments("build", "builds") .SetQueryParam("definitions", buildDefinition) .SetQueryParam("resultFilter", "succeeded") - .SetQueryParam("branchName", "master") + .SetQueryParam("branchName", "refs/heads/master") .SetQueryParam("$top", 1) .SetQueryParam("api-version", "6.1-preview.6"); From 9c117d2773593978584add0002090a856a13bbbb Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 23 Sep 2022 21:40:30 +0200 Subject: [PATCH 2/2] Nodes - Added gradient nodes Nodes - Added color gradient pin type Data bindings - Changed color gradient data bindings to now take a color gradient --- .../Artemis.Core.csproj.DotSettings | 4 +- .../Properties/ColorGradientLayerProperty.cs | 61 +--------- .../DataBindings/DataBindingProperty.cs | 2 +- .../DataBindings/IDataBindingProperty.cs | 2 +- .../Plugins/PluginFeatureAttribute.cs | 1 + src/Artemis.Core/Plugins/PluginFeatureInfo.cs | 50 +------- .../Internal/DataBindingExitNode.cs | 5 +- .../Internal/EventConditionEventStartNode.cs | 3 +- .../VisualScripting/NodeScript.cs | 6 +- .../{ => Nodes}/CustomNodeViewModel.cs | 0 .../Nodes/CustomNodeViewModelPosition.cs | 32 +++++ .../{ => Nodes}/DefaultNode.cs | 0 .../Nodes/ICustomViewModelNode.cs | 18 +++ .../VisualScripting/{ => Nodes}/Node.cs | 114 +----------------- .../VisualScripting/Nodes/NodeTStorage.cs | 52 ++++++++ .../Nodes/NodeTStorageTViewModel.cs | 58 +++++++++ .../VisualScripting/{ => Pins}/InputPin.cs | 0 .../{ => Pins}/InputPinCollection.cs | 0 .../{ => Pins}/ObjectOutputPins.cs | 0 .../VisualScripting/{ => Pins}/OutputPin.cs | 0 .../{ => Pins}/OutputPinCollection.cs | 0 .../VisualScripting/{ => Pins}/Pin.cs | 0 .../{ => Pins}/PinCollection.cs | 0 .../{ => Pins}/PinDirection.cs | 0 .../Migrations/M0021GradientNodes.cs | 87 +++++++++++++ .../Controls/ArtemisIcon.axaml.cs | 2 +- .../Controls/GradientPicker/GradientPicker.cs | 102 ++++++++++------ .../GradientPicker/GradientPickerButton.cs | 37 ++---- .../Commands/UpdateColorGradient.cs | 32 +++-- .../Styles/Controls/GradientPicker.axaml | 18 +-- .../Screens/Plugins/PluginFeatureView.axaml | 12 +- .../Screens/Plugins/PluginFeatureViewModel.cs | 42 +++++++ .../Panels/Playback/PlaybackViewModel.cs | 24 +++- .../ScriptConfigurationCreateView.axaml | 2 +- .../Screens/Scripting/ScriptsDialogView.axaml | 2 +- .../Sidebar/Dialogs/ProfileModuleViewModel.cs | 2 +- .../Screens/VisualScripting/CableView.axaml | 25 ++-- .../Screens/VisualScripting/NodeView.axaml | 29 +++-- .../Screens/VisualScripting/NodeViewModel.cs | 58 ++++++++- .../Services/RegistrationService.cs | 1 + .../Artemis.VisualScripting.csproj | 3 + .../Nodes/Color/ColorGradientFromPinsNode.cs | 102 ++++++++++++++++ .../Nodes/Color/ColorGradientNode.cs | 108 +++++++++++++++++ .../Nodes/Color/RampSKColorNode.cs | 2 +- .../Screens/ColorGradientNodeCustomView.axaml | 15 +++ .../ColorGradientNodeCustomView.axaml.cs | 27 +++++ .../ColorGradientNodeCustomViewModel.cs | 28 +++++ .../Nodes/DataModel/DataModelEventNode.cs | 6 + 48 files changed, 830 insertions(+), 344 deletions(-) rename src/Artemis.Core/VisualScripting/{ => Nodes}/CustomNodeViewModel.cs (100%) create mode 100644 src/Artemis.Core/VisualScripting/Nodes/CustomNodeViewModelPosition.cs rename src/Artemis.Core/VisualScripting/{ => Nodes}/DefaultNode.cs (100%) create mode 100644 src/Artemis.Core/VisualScripting/Nodes/ICustomViewModelNode.cs rename src/Artemis.Core/VisualScripting/{ => Nodes}/Node.cs (76%) create mode 100644 src/Artemis.Core/VisualScripting/Nodes/NodeTStorage.cs create mode 100644 src/Artemis.Core/VisualScripting/Nodes/NodeTStorageTViewModel.cs rename src/Artemis.Core/VisualScripting/{ => Pins}/InputPin.cs (100%) rename src/Artemis.Core/VisualScripting/{ => Pins}/InputPinCollection.cs (100%) rename src/Artemis.Core/VisualScripting/{ => Pins}/ObjectOutputPins.cs (100%) rename src/Artemis.Core/VisualScripting/{ => Pins}/OutputPin.cs (100%) rename src/Artemis.Core/VisualScripting/{ => Pins}/OutputPinCollection.cs (100%) rename src/Artemis.Core/VisualScripting/{ => Pins}/Pin.cs (100%) rename src/Artemis.Core/VisualScripting/{ => Pins}/PinCollection.cs (100%) rename src/Artemis.Core/VisualScripting/{ => Pins}/PinDirection.cs (100%) create mode 100644 src/Artemis.Storage/Migrations/M0021GradientNodes.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Color/ColorGradientFromPinsNode.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Color/ColorGradientNode.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Color/Screens/ColorGradientNodeCustomView.axaml create mode 100644 src/Artemis.VisualScripting/Nodes/Color/Screens/ColorGradientNodeCustomView.axaml.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Color/Screens/ColorGradientNodeCustomViewModel.cs diff --git a/src/Artemis.Core/Artemis.Core.csproj.DotSettings b/src/Artemis.Core/Artemis.Core.csproj.DotSettings index a18836e5e..c23015925 100644 --- a/src/Artemis.Core/Artemis.Core.csproj.DotSettings +++ b/src/Artemis.Core/Artemis.Core.csproj.DotSettings @@ -93,4 +93,6 @@ True True True - True \ No newline at end of file + True + True + True \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs index c88cbd625..b108d81f1 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs @@ -1,13 +1,8 @@ -using System.Collections.Specialized; -using SkiaSharp; - -namespace Artemis.Core; +namespace Artemis.Core; /// public class ColorGradientLayerProperty : LayerProperty { - private ColorGradient? _subscribedGradient; - internal ColorGradientLayerProperty() { KeyframesSupported = false; @@ -22,29 +17,6 @@ public class ColorGradientLayerProperty : LayerProperty return p.CurrentValue; } - #region Overrides of LayerProperty - - /// - protected override void OnCurrentValueSet() - { - // Don't allow color gradients to be null - if (BaseValue == null!) - BaseValue = new ColorGradient(DefaultValue); - - if (!ReferenceEquals(_subscribedGradient, BaseValue)) - { - if (_subscribedGradient != null) - _subscribedGradient.CollectionChanged -= SubscribedGradientOnPropertyChanged; - _subscribedGradient = BaseValue; - _subscribedGradient.CollectionChanged += SubscribedGradientOnPropertyChanged; - } - - CreateDataBindingRegistrations(); - base.OnCurrentValueSet(); - } - - #endregion - /// protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased) { @@ -60,33 +32,12 @@ public class ColorGradientLayerProperty : LayerProperty if (BaseValue == null!) BaseValue = new ColorGradient(DefaultValue); - base.OnInitialize(); + DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => + { + if (value != null) + CurrentValue = value; + }, "Value"); } #endregion - - private void CreateDataBindingRegistrations() - { - DataBinding.ClearDataBindingProperties(); - if (CurrentValue == null!) - return; - - for (int index = 0; index < CurrentValue.Count; index++) - { - int stopIndex = index; - - void Setter(SKColor value) - { - CurrentValue[stopIndex].Color = value; - } - - DataBinding.RegisterDataBindingProperty(() => CurrentValue[stopIndex].Color, Setter, $"Color #{stopIndex + 1}"); - } - } - - private void SubscribedGradientOnPropertyChanged(object? sender, NotifyCollectionChangedEventArgs args) - { - if (CurrentValue.Count != DataBinding.Properties.Count) - CreateDataBindingRegistrations(); - } } \ 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 index 13a5c9e27..81c4b719a 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingProperty.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingProperty.cs @@ -35,7 +35,7 @@ public class DataBindingProperty : IDataBindingProperty } /// - public void SetValue(object? value) + public void SetValue(object value) { // Numeric has a bunch of conversion, this seems the cheapest way to use them :) switch (value) diff --git a/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingProperty.cs b/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingProperty.cs index 7d5dc49bd..7e4ce8d06 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingProperty.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingProperty.cs @@ -27,5 +27,5 @@ public interface IDataBindingProperty /// Sets the value of the property this registration points to /// /// A value matching the type of - void SetValue(object? value); + void SetValue(object value); } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/PluginFeatureAttribute.cs b/src/Artemis.Core/Plugins/PluginFeatureAttribute.cs index 19acfa645..cf36770e1 100644 --- a/src/Artemis.Core/Plugins/PluginFeatureAttribute.cs +++ b/src/Artemis.Core/Plugins/PluginFeatureAttribute.cs @@ -22,6 +22,7 @@ public class PluginFeatureAttribute : Attribute /// The plugins display icon that's shown in the settings see for /// available icons /// + [Obsolete("Feature icons are no longer shown in the UI.")] public string? Icon { get; set; } /// diff --git a/src/Artemis.Core/Plugins/PluginFeatureInfo.cs b/src/Artemis.Core/Plugins/PluginFeatureInfo.cs index 35798a064..32d5775ec 100644 --- a/src/Artemis.Core/Plugins/PluginFeatureInfo.cs +++ b/src/Artemis.Core/Plugins/PluginFeatureInfo.cs @@ -31,20 +31,7 @@ public class PluginFeatureInfo : CorePropertyChanged, IPrerequisitesSubject Name = attribute?.Name ?? featureType.Name.Humanize(LetterCasing.Title); Description = attribute?.Description; - Icon = attribute?.Icon; AlwaysEnabled = attribute?.AlwaysEnabled ?? false; - - if (Icon != null) return; - if (typeof(DeviceProvider).IsAssignableFrom(featureType)) - Icon = "Devices"; - else if (typeof(Module).IsAssignableFrom(featureType)) - Icon = "VectorRectangle"; - else if (typeof(LayerBrushProvider).IsAssignableFrom(featureType)) - Icon = "Brush"; - else if (typeof(LayerEffectProvider).IsAssignableFrom(featureType)) - Icon = "AutoAwesome"; - else - Icon = "Plugin"; } internal PluginFeatureInfo(Plugin plugin, PluginFeatureAttribute? attribute, PluginFeature instance) @@ -56,19 +43,8 @@ public class PluginFeatureInfo : CorePropertyChanged, IPrerequisitesSubject Name = attribute?.Name ?? instance.GetType().Name.Humanize(LetterCasing.Title); Description = attribute?.Description; - Icon = attribute?.Icon; AlwaysEnabled = attribute?.AlwaysEnabled ?? false; Instance = instance; - - if (Icon != null) return; - Icon = Instance switch - { - DeviceProvider => "Devices", - Module => "VectorRectangle", - LayerBrushProvider => "Brush", - LayerEffectProvider => "AutoAwesome", - _ => "Plugin" - }; } /// @@ -110,17 +86,6 @@ public class PluginFeatureInfo : CorePropertyChanged, IPrerequisitesSubject set => SetAndNotify(ref _description, value); } - /// - /// The plugins display icon that's shown in the settings see for - /// available icons - /// - [JsonProperty] - public string? Icon - { - get => _icon; - set => SetAndNotify(ref _icon, value); - } - /// /// Marks the feature to always be enabled as long as the plugin is enabled and cannot be disabled. /// Note: always if this is the plugin's only feature @@ -142,20 +107,7 @@ public class PluginFeatureInfo : CorePropertyChanged, IPrerequisitesSubject get => _instance; internal set => SetAndNotify(ref _instance, value); } - - /// - /// Gets a string representing either a full path pointing to an svg or the markdown icon - /// - public string? ResolvedIcon - { - get - { - if (Icon == null) - return null; - return Icon.Contains('.') ? Plugin.ResolveRelativePath(Icon) : Icon; - } - } - + internal PluginFeatureEntity Entity { get; } /// diff --git a/src/Artemis.Core/VisualScripting/Internal/DataBindingExitNode.cs b/src/Artemis.Core/VisualScripting/Internal/DataBindingExitNode.cs index 6b4d367f5..7857f533d 100644 --- a/src/Artemis.Core/VisualScripting/Internal/DataBindingExitNode.cs +++ b/src/Artemis.Core/VisualScripting/Internal/DataBindingExitNode.cs @@ -23,7 +23,10 @@ internal class DataBindingExitNode : Node, IExitNode public void ApplyToDataBinding() { foreach ((IDataBindingProperty? property, object? pendingValue) in _propertyValues) - property.SetValue(pendingValue); + { + if (pendingValue != null) + property.SetValue(pendingValue); + } } public override void Evaluate() diff --git a/src/Artemis.Core/VisualScripting/Internal/EventConditionEventStartNode.cs b/src/Artemis.Core/VisualScripting/Internal/EventConditionEventStartNode.cs index f417e8871..14f1e0562 100644 --- a/src/Artemis.Core/VisualScripting/Internal/EventConditionEventStartNode.cs +++ b/src/Artemis.Core/VisualScripting/Internal/EventConditionEventStartNode.cs @@ -15,8 +15,7 @@ internal class EventConditionEventStartNode : DefaultNode public void SetDataModelEvent(IDataModelEvent? dataModelEvent) { - - } + } public void CreatePins(IDataModelEvent? dataModelEvent) { diff --git a/src/Artemis.Core/VisualScripting/NodeScript.cs b/src/Artemis.Core/VisualScripting/NodeScript.cs index 9bfb08011..7a02555ac 100644 --- a/src/Artemis.Core/VisualScripting/NodeScript.cs +++ b/src/Artemis.Core/VisualScripting/NodeScript.cs @@ -93,10 +93,8 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript NodeTypeStore.NodeTypeRemoved += NodeTypeStoreOnNodeTypeRemoved; if (defaultNodes != null) - { foreach (DefaultNode defaultNode in defaultNodes) AddNode(defaultNode); - } } internal NodeScript(string name, string description, NodeScriptEntity entity, object? context = null, List? defaultNodes = null) @@ -109,12 +107,10 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript NodeTypeStore.NodeTypeAdded += NodeTypeStoreOnNodeTypeAdded; NodeTypeStore.NodeTypeRemoved += NodeTypeStoreOnNodeTypeRemoved; - + if (defaultNodes != null) - { foreach (DefaultNode defaultNode in defaultNodes) AddNode(defaultNode); - } } #endregion diff --git a/src/Artemis.Core/VisualScripting/CustomNodeViewModel.cs b/src/Artemis.Core/VisualScripting/Nodes/CustomNodeViewModel.cs similarity index 100% rename from src/Artemis.Core/VisualScripting/CustomNodeViewModel.cs rename to src/Artemis.Core/VisualScripting/Nodes/CustomNodeViewModel.cs diff --git a/src/Artemis.Core/VisualScripting/Nodes/CustomNodeViewModelPosition.cs b/src/Artemis.Core/VisualScripting/Nodes/CustomNodeViewModelPosition.cs new file mode 100644 index 000000000..74616dd8e --- /dev/null +++ b/src/Artemis.Core/VisualScripting/Nodes/CustomNodeViewModelPosition.cs @@ -0,0 +1,32 @@ +namespace Artemis.Core; + +/// +/// Represents the position of a node's custom view model. +/// +public enum CustomNodeViewModelPosition +{ + /// + /// Puts the view model above the pins. + /// + AbovePins, + + /// + /// Puts the view model between the pins, vertically aligned to the top. + /// + BetweenPinsTop, + + /// + /// Puts the view model between the pins, vertically aligned to the center. + /// + BetweenPinsCenter, + + /// + /// Puts the view model between the pins, vertically aligned to the bottom. + /// + BetweenPinsBottom, + + /// + /// Puts the view model below the pins. + /// + BelowPins +} \ No newline at end of file diff --git a/src/Artemis.Core/VisualScripting/DefaultNode.cs b/src/Artemis.Core/VisualScripting/Nodes/DefaultNode.cs similarity index 100% rename from src/Artemis.Core/VisualScripting/DefaultNode.cs rename to src/Artemis.Core/VisualScripting/Nodes/DefaultNode.cs diff --git a/src/Artemis.Core/VisualScripting/Nodes/ICustomViewModelNode.cs b/src/Artemis.Core/VisualScripting/Nodes/ICustomViewModelNode.cs new file mode 100644 index 000000000..b16623935 --- /dev/null +++ b/src/Artemis.Core/VisualScripting/Nodes/ICustomViewModelNode.cs @@ -0,0 +1,18 @@ +namespace Artemis.Core; + +/// +/// Represents a node that has a custom view model. +/// +public interface ICustomViewModelNode +{ + /// + /// Gets or sets the position of the node's custom view model. + /// + CustomNodeViewModelPosition ViewModelPosition { get; } + + /// + /// Called whenever the node must show it's custom view model, if , no custom view model is used + /// + /// The custom view model, if , no custom view model is used + ICustomNodeViewModel? GetCustomViewModel(NodeScript nodeScript); +} \ No newline at end of file diff --git a/src/Artemis.Core/VisualScripting/Node.cs b/src/Artemis.Core/VisualScripting/Nodes/Node.cs similarity index 76% rename from src/Artemis.Core/VisualScripting/Node.cs rename to src/Artemis.Core/VisualScripting/Nodes/Node.cs index f212524de..e451f05aa 100644 --- a/src/Artemis.Core/VisualScripting/Node.cs +++ b/src/Artemis.Core/VisualScripting/Nodes/Node.cs @@ -2,10 +2,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using System.Reflection; using Artemis.Core.Events; -using Ninject; -using Ninject.Parameters; namespace Artemis.Core; @@ -339,7 +336,8 @@ public abstract class Node : BreakableModel, INode } /// - /// Called when the node was loaded from storage or newly created, at this point pin connections aren't reestablished yet. + /// Called when the node was loaded from storage or newly created, at this point pin connections aren't reestablished + /// yet. /// /// The script the node is contained in public virtual void Initialize(INodeScript script) @@ -374,16 +372,6 @@ public abstract class Node : BreakableModel, INode TryOrBreak(Evaluate, "Failed to evaluate"); } - /// - /// Called whenever the node must show it's custom view model, if , no custom view model is used - /// - /// - /// The custom view model, if , no custom view model is used - public virtual ICustomNodeViewModel? GetCustomViewModel(NodeScript nodeScript) - { - return null; - } - /// /// Serializes the object into a string /// @@ -402,102 +390,4 @@ public abstract class Node : BreakableModel, INode } #endregion -} - -/// -/// Represents a kind of node inside a containing storage value of type -/// . -/// -/// The type of value the node stores -public abstract class Node : Node -{ - private TStorage? _storage; - - /// - protected Node() - { - } - - /// - protected Node(string name, string description) : base(name, description) - { - } - - /// - /// Gets or sets the storage object of this node, this is saved across sessions - /// - public TStorage? Storage - { - get => _storage; - set - { - if (SetAndNotify(ref _storage, value)) - StorageModified?.Invoke(this, EventArgs.Empty); - } - } - - /// - /// Occurs whenever the storage of this node was modified. - /// - public event EventHandler? StorageModified; - - /// - public override string SerializeStorage() - { - return CoreJson.SerializeObject(Storage, true); - } - - /// - public override void DeserializeStorage(string serialized) - { - Storage = CoreJson.DeserializeObject(serialized) ?? default(TStorage); - } -} - -/// -/// Represents a kind of node inside a containing storage value of type -/// and a view model of type . -/// -/// The type of value the node stores -/// The type of view model the node uses -public abstract class Node : Node where TViewModel : ICustomNodeViewModel -{ - /// - protected Node() - { - } - - /// - protected Node(string name, string description) : base(name, description) - { - } - - [Inject] - internal IKernel Kernel { get; set; } = null!; - - /// - /// Called when a view model is required - /// - /// - public virtual TViewModel GetViewModel(NodeScript nodeScript) - { - // Limit to one constructor, there's no need to have more and it complicates things anyway - ConstructorInfo[] constructors = typeof(TViewModel).GetConstructors(); - if (constructors.Length != 1) - throw new ArtemisCoreException("Node VMs must have exactly one constructor"); - - // Find the ScriptConfiguration parameter, it is required by the base constructor so its there for sure - ParameterInfo? configurationParameter = constructors.First().GetParameters().FirstOrDefault(p => GetType().IsAssignableFrom(p.ParameterType)); - - if (configurationParameter?.Name == null) - throw new ArtemisCoreException($"Couldn't find a valid constructor argument on {typeof(TViewModel).Name} with type {GetType().Name}"); - return Kernel.Get(new ConstructorArgument(configurationParameter.Name, this), new ConstructorArgument("script", nodeScript)); - } - - /// - /// - public override ICustomNodeViewModel? GetCustomViewModel(NodeScript nodeScript) - { - return GetViewModel(nodeScript); - } } \ No newline at end of file diff --git a/src/Artemis.Core/VisualScripting/Nodes/NodeTStorage.cs b/src/Artemis.Core/VisualScripting/Nodes/NodeTStorage.cs new file mode 100644 index 000000000..81d9e7eac --- /dev/null +++ b/src/Artemis.Core/VisualScripting/Nodes/NodeTStorage.cs @@ -0,0 +1,52 @@ +using System; +using Artemis.Core; + +/// +/// Represents a kind of node inside a containing storage value of type +/// . +/// +/// The type of value the node stores +public abstract class Node : Node +{ + private TStorage? _storage; + + /// + protected Node() + { + } + + /// + protected Node(string name, string description) : base(name, description) + { + } + + /// + /// Gets or sets the storage object of this node, this is saved across sessions + /// + public TStorage? Storage + { + get => _storage; + set + { + if (SetAndNotify(ref _storage, value)) + StorageModified?.Invoke(this, EventArgs.Empty); + } + } + + /// + /// Occurs whenever the storage of this node was modified. + /// + public event EventHandler? StorageModified; + + /// + public override string SerializeStorage() + { + return CoreJson.SerializeObject(Storage, true); + } + + /// + public override void DeserializeStorage(string serialized) + { + Storage = CoreJson.DeserializeObject(serialized) ?? default(TStorage); + } +} \ No newline at end of file diff --git a/src/Artemis.Core/VisualScripting/Nodes/NodeTStorageTViewModel.cs b/src/Artemis.Core/VisualScripting/Nodes/NodeTStorageTViewModel.cs new file mode 100644 index 000000000..1b4109714 --- /dev/null +++ b/src/Artemis.Core/VisualScripting/Nodes/NodeTStorageTViewModel.cs @@ -0,0 +1,58 @@ +using System.Linq; +using System.Reflection; +using Artemis.Core; +using Ninject; +using Ninject.Parameters; + +/// +/// Represents a kind of node inside a containing storage value of type +/// and a view model of type . +/// +/// The type of value the node stores +/// The type of view model the node uses +public abstract class Node : Node, ICustomViewModelNode where TViewModel : ICustomNodeViewModel +{ + /// + protected Node() + { + } + + /// + protected Node(string name, string description) : base(name, description) + { + } + + [Inject] + internal IKernel Kernel { get; set; } = null!; + + /// + /// Called when a view model is required + /// + /// + public virtual TViewModel GetViewModel(NodeScript nodeScript) + { + // Limit to one constructor, there's no need to have more and it complicates things anyway + ConstructorInfo[] constructors = typeof(TViewModel).GetConstructors(); + if (constructors.Length != 1) + throw new ArtemisCoreException("Node VMs must have exactly one constructor"); + + // Find the ScriptConfiguration parameter, it is required by the base constructor so its there for sure + ParameterInfo? configurationParameter = constructors.First().GetParameters().FirstOrDefault(p => GetType().IsAssignableFrom(p.ParameterType)); + + if (configurationParameter?.Name == null) + throw new ArtemisCoreException($"Couldn't find a valid constructor argument on {typeof(TViewModel).Name} with type {GetType().Name}"); + return Kernel.Get(new ConstructorArgument(configurationParameter.Name, this), new ConstructorArgument("script", nodeScript)); + } + + /// + /// Gets or sets the position of the node's custom view model. + /// + public CustomNodeViewModelPosition ViewModelPosition { get; protected set; } = CustomNodeViewModelPosition.BetweenPinsTop; + + /// + /// + public ICustomNodeViewModel GetCustomViewModel(NodeScript nodeScript) + { + return GetViewModel(nodeScript); + } +} \ No newline at end of file diff --git a/src/Artemis.Core/VisualScripting/InputPin.cs b/src/Artemis.Core/VisualScripting/Pins/InputPin.cs similarity index 100% rename from src/Artemis.Core/VisualScripting/InputPin.cs rename to src/Artemis.Core/VisualScripting/Pins/InputPin.cs diff --git a/src/Artemis.Core/VisualScripting/InputPinCollection.cs b/src/Artemis.Core/VisualScripting/Pins/InputPinCollection.cs similarity index 100% rename from src/Artemis.Core/VisualScripting/InputPinCollection.cs rename to src/Artemis.Core/VisualScripting/Pins/InputPinCollection.cs diff --git a/src/Artemis.Core/VisualScripting/ObjectOutputPins.cs b/src/Artemis.Core/VisualScripting/Pins/ObjectOutputPins.cs similarity index 100% rename from src/Artemis.Core/VisualScripting/ObjectOutputPins.cs rename to src/Artemis.Core/VisualScripting/Pins/ObjectOutputPins.cs diff --git a/src/Artemis.Core/VisualScripting/OutputPin.cs b/src/Artemis.Core/VisualScripting/Pins/OutputPin.cs similarity index 100% rename from src/Artemis.Core/VisualScripting/OutputPin.cs rename to src/Artemis.Core/VisualScripting/Pins/OutputPin.cs diff --git a/src/Artemis.Core/VisualScripting/OutputPinCollection.cs b/src/Artemis.Core/VisualScripting/Pins/OutputPinCollection.cs similarity index 100% rename from src/Artemis.Core/VisualScripting/OutputPinCollection.cs rename to src/Artemis.Core/VisualScripting/Pins/OutputPinCollection.cs diff --git a/src/Artemis.Core/VisualScripting/Pin.cs b/src/Artemis.Core/VisualScripting/Pins/Pin.cs similarity index 100% rename from src/Artemis.Core/VisualScripting/Pin.cs rename to src/Artemis.Core/VisualScripting/Pins/Pin.cs diff --git a/src/Artemis.Core/VisualScripting/PinCollection.cs b/src/Artemis.Core/VisualScripting/Pins/PinCollection.cs similarity index 100% rename from src/Artemis.Core/VisualScripting/PinCollection.cs rename to src/Artemis.Core/VisualScripting/Pins/PinCollection.cs diff --git a/src/Artemis.Core/VisualScripting/PinDirection.cs b/src/Artemis.Core/VisualScripting/Pins/PinDirection.cs similarity index 100% rename from src/Artemis.Core/VisualScripting/PinDirection.cs rename to src/Artemis.Core/VisualScripting/Pins/PinDirection.cs diff --git a/src/Artemis.Storage/Migrations/M0021GradientNodes.cs b/src/Artemis.Storage/Migrations/M0021GradientNodes.cs new file mode 100644 index 000000000..11f062bd7 --- /dev/null +++ b/src/Artemis.Storage/Migrations/M0021GradientNodes.cs @@ -0,0 +1,87 @@ +using System; +using System.Linq; +using Artemis.Storage.Entities.Profile; +using Artemis.Storage.Entities.Profile.Nodes; +using Artemis.Storage.Migrations.Interfaces; +using LiteDB; + +namespace Artemis.Storage.Migrations; + +public class M0021GradientNodes : IStorageMigration +{ + private void MigrateDataBinding(PropertyEntity property) + { + NodeScriptEntity script = property.DataBinding.NodeScript; + NodeEntity exitNode = script.Nodes.FirstOrDefault(s => s.IsExitNode); + if (exitNode == null) + return; + + // Create a new node at the same position of the exit node + NodeEntity gradientNode = new() + { + Id = Guid.NewGuid(), + Type = "ColorGradientNode", + PluginId = Guid.Parse("ffffffff-ffff-ffff-ffff-ffffffffffff"), + Name = "Color Gradient", + Description = "Outputs a color gradient with the given colors", + X = exitNode.X, + Y = exitNode.Y, + Storage = property.Value // Copy the value of the property into the node storage + }; + script.Nodes.Add(gradientNode); + + // Move all connections of the exit node to the new node + foreach (NodeConnectionEntity connection in script.Connections) + { + if (connection.SourceNode == exitNode.Id) + { + connection.SourceNode = gradientNode.Id; + connection.SourcePinId++; + } + } + + // Connect the data binding node to the source node + script.Connections.Add(new NodeConnectionEntity + { + SourceType = "ColorGradient", + SourceNode = exitNode.Id, + SourcePinCollectionId = -1, + SourcePinId = 0, + TargetType = "ColorGradient", + TargetNode = gradientNode.Id, + TargetPinCollectionId = -1, + TargetPinId = 0, + }); + + // Move the exit node to the right + exitNode.X += 300; + exitNode.Y += 30; + } + + public int UserVersion => 21; + + public void Apply(LiteRepository repository) + { + // Find all color gradient data bindings, there's no really good way to do this so infer it from the value + ILiteCollection collection = repository.Database.GetCollection(); + foreach (ProfileEntity profileEntity in collection.FindAll()) + { + foreach (LayerEntity layer in profileEntity.Layers) + MigrateDataBinding(layer.LayerBrush.PropertyGroup); + + collection.Update(profileEntity); + } + } + + private void MigrateDataBinding(PropertyGroupEntity propertyGroup) + { + foreach (PropertyGroupEntity propertyGroupPropertyGroup in propertyGroup.PropertyGroups) + MigrateDataBinding(propertyGroupPropertyGroup); + + foreach (PropertyEntity property in propertyGroup.Properties) + { + if (property.Value.StartsWith("[{\"Color\":\"") && property.DataBinding?.NodeScript != null && property.DataBinding.IsEnabled) + MigrateDataBinding(property); + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Controls/ArtemisIcon.axaml.cs b/src/Artemis.UI.Shared/Controls/ArtemisIcon.axaml.cs index e60e2e73b..1e3e47a1d 100644 --- a/src/Artemis.UI.Shared/Controls/ArtemisIcon.axaml.cs +++ b/src/Artemis.UI.Shared/Controls/ArtemisIcon.axaml.cs @@ -129,7 +129,7 @@ public class ArtemisIcon : UserControl /// theme /// public static readonly StyledProperty FillProperty = - AvaloniaProperty.Register(nameof(Icon), true, notifying: IconChanging); + AvaloniaProperty.Register(nameof(Icon), false, notifying: IconChanging); /// /// Gets or sets a boolean indicating whether or not the icon should be filled in with the primary text color of the diff --git a/src/Artemis.UI.Shared/Controls/GradientPicker/GradientPicker.cs b/src/Artemis.UI.Shared/Controls/GradientPicker/GradientPicker.cs index e98be34a9..0066ad760 100644 --- a/src/Artemis.UI.Shared/Controls/GradientPicker/GradientPicker.cs +++ b/src/Artemis.UI.Shared/Controls/GradientPicker/GradientPicker.cs @@ -59,22 +59,29 @@ public class GradientPicker : TemplatedControl public static readonly DirectProperty DeleteStopProperty = AvaloniaProperty.RegisterDirect(nameof(DeleteStop), g => g.DeleteStop); + /// + /// Gets the color gradient currently being edited, for internal use only + /// + public static readonly DirectProperty EditingColorGradientProperty = + AvaloniaProperty.RegisterDirect(nameof(EditingColorGradient), g => g.EditingColorGradient); + private readonly ICommand _deleteStop; private ColorPicker? _colorPicker; private Button? _flipStops; private Border? _gradient; - private ColorGradient? _lastColorGradient; private Button? _randomize; private Button? _rotateStops; private bool _shiftDown; private Button? _spreadStops; private Button? _toggleSeamless; + private ColorGradient _colorGradient = null!; /// /// Creates a new instance of the class. /// public GradientPicker() { + EditingColorGradient = ColorGradient.GetUnicornBarf(); _deleteStop = ReactiveCommand.Create(s => { if (ColorGradient.Count <= 2) @@ -143,6 +150,15 @@ public class GradientPicker : TemplatedControl get => _deleteStop; private init => SetAndRaise(DeleteStopProperty, ref _deleteStop, value); } + + /// + /// Gets the color gradient backing the editor, this is a copy of . + /// + public ColorGradient EditingColorGradient + { + get => _colorGradient; + private set => SetAndRaise(EditingColorGradientProperty, ref _colorGradient, value); + } /// protected override void OnApplyTemplate(TemplateAppliedEventArgs e) @@ -167,7 +183,7 @@ public class GradientPicker : TemplatedControl _flipStops = e.NameScope.Find