Nodes - Added gradient nodes

Nodes - Added color gradient pin type
Data bindings - Changed color gradient data bindings to now take a color gradient
This commit is contained in:
Robert committed 2022-09-23 21:41:08 +02:00
1 parent 32a444fbeb
commit 9c117d2773
48 files changed
+830 -344

No files matched your search

@@ -93,4 +93,6 @@
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=stores_005Cregistrations/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=utilities/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=visualscripting/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=visualscripting_005Cinterfaces/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=visualscripting_005Cinterfaces/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=visualscripting_005Cnodes/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=visualscripting_005Cpins/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
@@ -1,13 +1,8 @@
using System.Collections.Specialized;
using SkiaSharp;
namespace Artemis.Core;
namespace Artemis.Core;
/// <inheritdoc />
public class ColorGradientLayerProperty : LayerProperty<ColorGradient>
{
private ColorGradient? _subscribedGradient;
internal ColorGradientLayerProperty()
{
KeyframesSupported = false;
@@ -22,29 +17,6 @@ public class ColorGradientLayerProperty : LayerProperty<ColorGradient>
return p.CurrentValue;
}
#region Overrides of LayerProperty<ColorGradient>
/// <inheritdoc />
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
/// <inheritdoc />
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
{
@@ -60,33 +32,12 @@ public class ColorGradientLayerProperty : LayerProperty<ColorGradient>
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();
}
}
@@ -35,7 +35,7 @@ public class DataBindingProperty<TProperty> : IDataBindingProperty
}
/// <inheritdoc />
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)
@@ -27,5 +27,5 @@ public interface IDataBindingProperty
/// Sets the value of the property this registration points to
/// </summary>
/// <param name="value">A value matching the type of <see cref="ValueType" /></param>
void SetValue(object? value);
void SetValue(object value);
}
@@ -22,6 +22,7 @@ public class PluginFeatureAttribute : Attribute
/// The plugins display icon that's shown in the settings see <see href="https://materialdesignicons.com" /> for
/// available icons
/// </summary>
[Obsolete("Feature icons are no longer shown in the UI.")]
public string? Icon { get; set; }
/// <summary>
+1 -49
View File
@@ -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"
};
}
/// <summary>
@@ -110,17 +86,6 @@ public class PluginFeatureInfo : CorePropertyChanged, IPrerequisitesSubject
set => SetAndNotify(ref _description, value);
}
/// <summary>
/// The plugins display icon that's shown in the settings see <see href="https://materialdesignicons.com" /> for
/// available icons
/// </summary>
[JsonProperty]
public string? Icon
{
get => _icon;
set => SetAndNotify(ref _icon, value);
}
/// <summary>
/// Marks the feature to always be enabled as long as the plugin is enabled and cannot be disabled.
/// <para>Note: always <see langword="true" /> if this is the plugin's only feature</para>
@@ -142,20 +107,7 @@ public class PluginFeatureInfo : CorePropertyChanged, IPrerequisitesSubject
get => _instance;
internal set => SetAndNotify(ref _instance, value);
}
/// <summary>
/// Gets a string representing either a full path pointing to an svg or the markdown icon
/// </summary>
public string? ResolvedIcon
{
get
{
if (Icon == null)
return null;
return Icon.Contains('.') ? Plugin.ResolveRelativePath(Icon) : Icon;
}
}
internal PluginFeatureEntity Entity { get; }
/// <inheritdoc />
@@ -23,7 +23,10 @@ internal class DataBindingExitNode<TLayerProperty> : 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()
@@ -15,8 +15,7 @@ internal class EventConditionEventStartNode : DefaultNode
public void SetDataModelEvent(IDataModelEvent? dataModelEvent)
{
}
}
public void CreatePins(IDataModelEvent? dataModelEvent)
{
@@ -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<DefaultNode>? 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
@@ -0,0 +1,32 @@
namespace Artemis.Core;
/// <summary>
/// Represents the position of a node's custom view model.
/// </summary>
public enum CustomNodeViewModelPosition
{
/// <summary>
/// Puts the view model above the pins.
/// </summary>
AbovePins,
/// <summary>
/// Puts the view model between the pins, vertically aligned to the top.
/// </summary>
BetweenPinsTop,
/// <summary>
/// Puts the view model between the pins, vertically aligned to the center.
/// </summary>
BetweenPinsCenter,
/// <summary>
/// Puts the view model between the pins, vertically aligned to the bottom.
/// </summary>
BetweenPinsBottom,
/// <summary>
/// Puts the view model below the pins.
/// </summary>
BelowPins
}
@@ -0,0 +1,18 @@
namespace Artemis.Core;
/// <summary>
/// Represents a node that has a custom view model.
/// </summary>
public interface ICustomViewModelNode
{
/// <summary>
/// Gets or sets the position of the node's custom view model.
/// </summary>
CustomNodeViewModelPosition ViewModelPosition { get; }
/// <summary>
/// Called whenever the node must show it's custom view model, if <see langword="null" />, no custom view model is used
/// </summary>
/// <returns>The custom view model, if <see langword="null" />, no custom view model is used</returns>
ICustomNodeViewModel? GetCustomViewModel(NodeScript nodeScript);
}
@@ -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
}
/// <summary>
/// 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.
/// </summary>
/// <param name="script">The script the node is contained in</param>
public virtual void Initialize(INodeScript script)
@@ -374,16 +372,6 @@ public abstract class Node : BreakableModel, INode
TryOrBreak(Evaluate, "Failed to evaluate");
}
/// <summary>
/// Called whenever the node must show it's custom view model, if <see langword="null" />, no custom view model is used
/// </summary>
/// <param name="nodeScript"></param>
/// <returns>The custom view model, if <see langword="null" />, no custom view model is used</returns>
public virtual ICustomNodeViewModel? GetCustomViewModel(NodeScript nodeScript)
{
return null;
}
/// <summary>
/// Serializes the <see cref="Storage" /> object into a string
/// </summary>
@@ -402,102 +390,4 @@ public abstract class Node : BreakableModel, INode
}
#endregion
}
/// <summary>
/// Represents a kind of node inside a <see cref="NodeScript" /> containing storage value of type
/// <typeparamref name="TStorage" />.
/// </summary>
/// <typeparam name="TStorage">The type of value the node stores</typeparam>
public abstract class Node<TStorage> : Node
{
private TStorage? _storage;
/// <inheritdoc />
protected Node()
{
}
/// <inheritdoc />
protected Node(string name, string description) : base(name, description)
{
}
/// <summary>
/// Gets or sets the storage object of this node, this is saved across sessions
/// </summary>
public TStorage? Storage
{
get => _storage;
set
{
if (SetAndNotify(ref _storage, value))
StorageModified?.Invoke(this, EventArgs.Empty);
}
}
/// <summary>
/// Occurs whenever the storage of this node was modified.
/// </summary>
public event EventHandler? StorageModified;
/// <inheritdoc />
public override string SerializeStorage()
{
return CoreJson.SerializeObject(Storage, true);
}
/// <inheritdoc />
public override void DeserializeStorage(string serialized)
{
Storage = CoreJson.DeserializeObject<TStorage>(serialized) ?? default(TStorage);
}
}
/// <summary>
/// Represents a kind of node inside a <see cref="NodeScript" /> containing storage value of type
/// <typeparamref name="TStorage" /> and a view model of type <typeparamref name="TViewModel" />.
/// </summary>
/// <typeparam name="TStorage">The type of value the node stores</typeparam>
/// <typeparam name="TViewModel">The type of view model the node uses</typeparam>
public abstract class Node<TStorage, TViewModel> : Node<TStorage> where TViewModel : ICustomNodeViewModel
{
/// <inheritdoc />
protected Node()
{
}
/// <inheritdoc />
protected Node(string name, string description) : base(name, description)
{
}
[Inject]
internal IKernel Kernel { get; set; } = null!;
/// <summary>
/// Called when a view model is required
/// </summary>
/// <param name="nodeScript"></param>
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<TViewModel>(new ConstructorArgument(configurationParameter.Name, this), new ConstructorArgument("script", nodeScript));
}
/// <param name="nodeScript"></param>
/// <inheritdoc />
public override ICustomNodeViewModel? GetCustomViewModel(NodeScript nodeScript)
{
return GetViewModel(nodeScript);
}
}
@@ -0,0 +1,52 @@
using System;
using Artemis.Core;
/// <summary>
/// Represents a kind of node inside a <see cref="NodeScript" /> containing storage value of type
/// <typeparamref name="TStorage" />.
/// </summary>
/// <typeparam name="TStorage">The type of value the node stores</typeparam>
public abstract class Node<TStorage> : Node
{
private TStorage? _storage;
/// <inheritdoc />
protected Node()
{
}
/// <inheritdoc />
protected Node(string name, string description) : base(name, description)
{
}
/// <summary>
/// Gets or sets the storage object of this node, this is saved across sessions
/// </summary>
public TStorage? Storage
{
get => _storage;
set
{
if (SetAndNotify(ref _storage, value))
StorageModified?.Invoke(this, EventArgs.Empty);
}
}
/// <summary>
/// Occurs whenever the storage of this node was modified.
/// </summary>
public event EventHandler? StorageModified;
/// <inheritdoc />
public override string SerializeStorage()
{
return CoreJson.SerializeObject(Storage, true);
}
/// <inheritdoc />
public override void DeserializeStorage(string serialized)
{
Storage = CoreJson.DeserializeObject<TStorage>(serialized) ?? default(TStorage);
}
}
@@ -0,0 +1,58 @@
using System.Linq;
using System.Reflection;
using Artemis.Core;
using Ninject;
using Ninject.Parameters;
/// <summary>
/// Represents a kind of node inside a <see cref="NodeScript" /> containing storage value of type
/// <typeparamref name="TStorage" /> and a view model of type <typeparamref name="TViewModel" />.
/// </summary>
/// <typeparam name="TStorage">The type of value the node stores</typeparam>
/// <typeparam name="TViewModel">The type of view model the node uses</typeparam>
public abstract class Node<TStorage, TViewModel> : Node<TStorage>, ICustomViewModelNode where TViewModel : ICustomNodeViewModel
{
/// <inheritdoc />
protected Node()
{
}
/// <inheritdoc />
protected Node(string name, string description) : base(name, description)
{
}
[Inject]
internal IKernel Kernel { get; set; } = null!;
/// <summary>
/// Called when a view model is required
/// </summary>
/// <param name="nodeScript"></param>
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<TViewModel>(new ConstructorArgument(configurationParameter.Name, this), new ConstructorArgument("script", nodeScript));
}
/// <summary>
/// Gets or sets the position of the node's custom view model.
/// </summary>
public CustomNodeViewModelPosition ViewModelPosition { get; protected set; } = CustomNodeViewModelPosition.BetweenPinsTop;
/// <param name="nodeScript"></param>
/// <inheritdoc />
public ICustomNodeViewModel GetCustomViewModel(NodeScript nodeScript)
{
return GetViewModel(nodeScript);
}
}