mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-11 19:36:28 +00:00
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:
1 parent
32a444fbeb
commit
9c117d2773
48 files changed
+813
-327
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/=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/=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/@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;
|
namespace Artemis.Core;
|
||||||
using SkiaSharp;
|
|
||||||
|
|
||||||
namespace Artemis.Core;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public class ColorGradientLayerProperty : LayerProperty<ColorGradient>
|
public class ColorGradientLayerProperty : LayerProperty<ColorGradient>
|
||||||
{
|
{
|
||||||
private ColorGradient? _subscribedGradient;
|
|
||||||
|
|
||||||
internal ColorGradientLayerProperty()
|
internal ColorGradientLayerProperty()
|
||||||
{
|
{
|
||||||
KeyframesSupported = false;
|
KeyframesSupported = false;
|
||||||
@@ -22,29 +17,6 @@ public class ColorGradientLayerProperty : LayerProperty<ColorGradient>
|
|||||||
return p.CurrentValue;
|
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 />
|
/// <inheritdoc />
|
||||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||||
{
|
{
|
||||||
@@ -60,33 +32,12 @@ public class ColorGradientLayerProperty : LayerProperty<ColorGradient>
|
|||||||
if (BaseValue == null!)
|
if (BaseValue == null!)
|
||||||
BaseValue = new ColorGradient(DefaultValue);
|
BaseValue = new ColorGradient(DefaultValue);
|
||||||
|
|
||||||
base.OnInitialize();
|
DataBinding.RegisterDataBindingProperty(() => CurrentValue, value =>
|
||||||
|
{
|
||||||
|
if (value != null)
|
||||||
|
CurrentValue = value;
|
||||||
|
}, "Value");
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#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 />
|
/// <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 :)
|
// Numeric has a bunch of conversion, this seems the cheapest way to use them :)
|
||||||
switch (value)
|
switch (value)
|
||||||
|
|||||||
@@ -27,5 +27,5 @@ public interface IDataBindingProperty
|
|||||||
/// Sets the value of the property this registration points to
|
/// Sets the value of the property this registration points to
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="value">A value matching the type of <see cref="ValueType" /></param>
|
/// <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
|
/// The plugins display icon that's shown in the settings see <see href="https://materialdesignicons.com" /> for
|
||||||
/// available icons
|
/// available icons
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Feature icons are no longer shown in the UI.")]
|
||||||
public string? Icon { get; set; }
|
public string? Icon { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -31,20 +31,7 @@ public class PluginFeatureInfo : CorePropertyChanged, IPrerequisitesSubject
|
|||||||
|
|
||||||
Name = attribute?.Name ?? featureType.Name.Humanize(LetterCasing.Title);
|
Name = attribute?.Name ?? featureType.Name.Humanize(LetterCasing.Title);
|
||||||
Description = attribute?.Description;
|
Description = attribute?.Description;
|
||||||
Icon = attribute?.Icon;
|
|
||||||
AlwaysEnabled = attribute?.AlwaysEnabled ?? false;
|
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)
|
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);
|
Name = attribute?.Name ?? instance.GetType().Name.Humanize(LetterCasing.Title);
|
||||||
Description = attribute?.Description;
|
Description = attribute?.Description;
|
||||||
Icon = attribute?.Icon;
|
|
||||||
AlwaysEnabled = attribute?.AlwaysEnabled ?? false;
|
AlwaysEnabled = attribute?.AlwaysEnabled ?? false;
|
||||||
Instance = instance;
|
Instance = instance;
|
||||||
|
|
||||||
if (Icon != null) return;
|
|
||||||
Icon = Instance switch
|
|
||||||
{
|
|
||||||
DeviceProvider => "Devices",
|
|
||||||
Module => "VectorRectangle",
|
|
||||||
LayerBrushProvider => "Brush",
|
|
||||||
LayerEffectProvider => "AutoAwesome",
|
|
||||||
_ => "Plugin"
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -110,17 +86,6 @@ public class PluginFeatureInfo : CorePropertyChanged, IPrerequisitesSubject
|
|||||||
set => SetAndNotify(ref _description, value);
|
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>
|
/// <summary>
|
||||||
/// Marks the feature to always be enabled as long as the plugin is enabled and cannot be disabled.
|
/// 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>
|
/// <para>Note: always <see langword="true" /> if this is the plugin's only feature</para>
|
||||||
@@ -143,19 +108,6 @@ public class PluginFeatureInfo : CorePropertyChanged, IPrerequisitesSubject
|
|||||||
internal set => SetAndNotify(ref _instance, value);
|
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; }
|
internal PluginFeatureEntity Entity { get; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@@ -23,8 +23,11 @@ internal class DataBindingExitNode<TLayerProperty> : Node, IExitNode
|
|||||||
public void ApplyToDataBinding()
|
public void ApplyToDataBinding()
|
||||||
{
|
{
|
||||||
foreach ((IDataBindingProperty? property, object? pendingValue) in _propertyValues)
|
foreach ((IDataBindingProperty? property, object? pendingValue) in _propertyValues)
|
||||||
|
{
|
||||||
|
if (pendingValue != null)
|
||||||
property.SetValue(pendingValue);
|
property.SetValue(pendingValue);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void Evaluate()
|
public override void Evaluate()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ internal class EventConditionEventStartNode : DefaultNode
|
|||||||
|
|
||||||
public void SetDataModelEvent(IDataModelEvent? dataModelEvent)
|
public void SetDataModelEvent(IDataModelEvent? dataModelEvent)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CreatePins(IDataModelEvent? dataModelEvent)
|
public void CreatePins(IDataModelEvent? dataModelEvent)
|
||||||
|
|||||||
@@ -93,11 +93,9 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript
|
|||||||
NodeTypeStore.NodeTypeRemoved += NodeTypeStoreOnNodeTypeRemoved;
|
NodeTypeStore.NodeTypeRemoved += NodeTypeStoreOnNodeTypeRemoved;
|
||||||
|
|
||||||
if (defaultNodes != null)
|
if (defaultNodes != null)
|
||||||
{
|
|
||||||
foreach (DefaultNode defaultNode in defaultNodes)
|
foreach (DefaultNode defaultNode in defaultNodes)
|
||||||
AddNode(defaultNode);
|
AddNode(defaultNode);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
internal NodeScript(string name, string description, NodeScriptEntity entity, object? context = null, List<DefaultNode>? defaultNodes = null)
|
internal NodeScript(string name, string description, NodeScriptEntity entity, object? context = null, List<DefaultNode>? defaultNodes = null)
|
||||||
{
|
{
|
||||||
@@ -111,11 +109,9 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript
|
|||||||
NodeTypeStore.NodeTypeRemoved += NodeTypeStoreOnNodeTypeRemoved;
|
NodeTypeStore.NodeTypeRemoved += NodeTypeStoreOnNodeTypeRemoved;
|
||||||
|
|
||||||
if (defaultNodes != null)
|
if (defaultNodes != null)
|
||||||
{
|
|
||||||
foreach (DefaultNode defaultNode in defaultNodes)
|
foreach (DefaultNode defaultNode in defaultNodes)
|
||||||
AddNode(defaultNode);
|
AddNode(defaultNode);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|||||||
File renamed without changes.
@@ -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
|
||||||
|
}
|
||||||
File renamed without changes.
@@ -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
-112
@@ -2,10 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
|
||||||
using Artemis.Core.Events;
|
using Artemis.Core.Events;
|
||||||
using Ninject;
|
|
||||||
using Ninject.Parameters;
|
|
||||||
|
|
||||||
namespace Artemis.Core;
|
namespace Artemis.Core;
|
||||||
|
|
||||||
@@ -339,7 +336,8 @@ public abstract class Node : BreakableModel, INode
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
/// <param name="script">The script the node is contained in</param>
|
/// <param name="script">The script the node is contained in</param>
|
||||||
public virtual void Initialize(INodeScript script)
|
public virtual void Initialize(INodeScript script)
|
||||||
@@ -374,16 +372,6 @@ public abstract class Node : BreakableModel, INode
|
|||||||
TryOrBreak(Evaluate, "Failed to evaluate");
|
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>
|
/// <summary>
|
||||||
/// Serializes the <see cref="Storage" /> object into a string
|
/// Serializes the <see cref="Storage" /> object into a string
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -403,101 +391,3 @@ public abstract class Node : BreakableModel, INode
|
|||||||
|
|
||||||
#endregion
|
#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);
|
||||||
|
}
|
||||||
|
}
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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<ProfileEntity> collection = repository.Database.GetCollection<ProfileEntity>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -129,7 +129,7 @@ public class ArtemisIcon : UserControl
|
|||||||
/// theme
|
/// theme
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly StyledProperty<bool> FillProperty =
|
public static readonly StyledProperty<bool> FillProperty =
|
||||||
AvaloniaProperty.Register<ArtemisIcon, bool>(nameof(Icon), true, notifying: IconChanging);
|
AvaloniaProperty.Register<ArtemisIcon, bool>(nameof(Icon), false, notifying: IconChanging);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a boolean indicating whether or not the icon should be filled in with the primary text color of the
|
/// Gets or sets a boolean indicating whether or not the icon should be filled in with the primary text color of the
|
||||||
|
|||||||
@@ -59,22 +59,29 @@ public class GradientPicker : TemplatedControl
|
|||||||
public static readonly DirectProperty<GradientPicker, ICommand> DeleteStopProperty =
|
public static readonly DirectProperty<GradientPicker, ICommand> DeleteStopProperty =
|
||||||
AvaloniaProperty.RegisterDirect<GradientPicker, ICommand>(nameof(DeleteStop), g => g.DeleteStop);
|
AvaloniaProperty.RegisterDirect<GradientPicker, ICommand>(nameof(DeleteStop), g => g.DeleteStop);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the color gradient currently being edited, for internal use only
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DirectProperty<GradientPicker, ColorGradient> EditingColorGradientProperty =
|
||||||
|
AvaloniaProperty.RegisterDirect<GradientPicker, ColorGradient>(nameof(EditingColorGradient), g => g.EditingColorGradient);
|
||||||
|
|
||||||
private readonly ICommand _deleteStop;
|
private readonly ICommand _deleteStop;
|
||||||
private ColorPicker? _colorPicker;
|
private ColorPicker? _colorPicker;
|
||||||
private Button? _flipStops;
|
private Button? _flipStops;
|
||||||
private Border? _gradient;
|
private Border? _gradient;
|
||||||
private ColorGradient? _lastColorGradient;
|
|
||||||
private Button? _randomize;
|
private Button? _randomize;
|
||||||
private Button? _rotateStops;
|
private Button? _rotateStops;
|
||||||
private bool _shiftDown;
|
private bool _shiftDown;
|
||||||
private Button? _spreadStops;
|
private Button? _spreadStops;
|
||||||
private Button? _toggleSeamless;
|
private Button? _toggleSeamless;
|
||||||
|
private ColorGradient _colorGradient = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of the <see cref="GradientPicker" /> class.
|
/// Creates a new instance of the <see cref="GradientPicker" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public GradientPicker()
|
public GradientPicker()
|
||||||
{
|
{
|
||||||
|
EditingColorGradient = ColorGradient.GetUnicornBarf();
|
||||||
_deleteStop = ReactiveCommand.Create<ColorGradientStop>(s =>
|
_deleteStop = ReactiveCommand.Create<ColorGradientStop>(s =>
|
||||||
{
|
{
|
||||||
if (ColorGradient.Count <= 2)
|
if (ColorGradient.Count <= 2)
|
||||||
@@ -144,6 +151,15 @@ public class GradientPicker : TemplatedControl
|
|||||||
private init => SetAndRaise(DeleteStopProperty, ref _deleteStop, value);
|
private init => SetAndRaise(DeleteStopProperty, ref _deleteStop, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the color gradient backing the editor, this is a copy of <see cref="ColorGradient"/>.
|
||||||
|
/// </summary>
|
||||||
|
public ColorGradient EditingColorGradient
|
||||||
|
{
|
||||||
|
get => _colorGradient;
|
||||||
|
private set => SetAndRaise(EditingColorGradientProperty, ref _colorGradient, value);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||||
{
|
{
|
||||||
@@ -188,7 +204,7 @@ public class GradientPicker : TemplatedControl
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||||
{
|
{
|
||||||
Subscribe();
|
ApplyToField();
|
||||||
|
|
||||||
KeyUp += OnKeyUp;
|
KeyUp += OnKeyUp;
|
||||||
KeyDown += OnKeyDown;
|
KeyDown += OnKeyDown;
|
||||||
@@ -197,60 +213,72 @@ public class GradientPicker : TemplatedControl
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||||
{
|
{
|
||||||
Unsubscribe();
|
ApplyToProperty();
|
||||||
KeyUp -= OnKeyUp;
|
KeyUp -= OnKeyUp;
|
||||||
KeyDown -= OnKeyDown;
|
KeyDown -= OnKeyDown;
|
||||||
|
|
||||||
_shiftDown = false;
|
_shiftDown = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static void ColorGradientChanged(IAvaloniaObject sender, bool before)
|
private static void ColorGradientChanged(IAvaloniaObject sender, bool before)
|
||||||
{
|
{
|
||||||
(sender as GradientPicker)?.Subscribe();
|
(sender as GradientPicker)?.ApplyToField();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void StorageProviderChanged(IAvaloniaObject sender, bool before)
|
private static void StorageProviderChanged(IAvaloniaObject sender, bool before)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Subscribe()
|
private void ApplyToField()
|
||||||
{
|
{
|
||||||
Unsubscribe();
|
EditingColorGradient = new ColorGradient(ColorGradient);
|
||||||
|
EditingColorGradient.CollectionChanged += ColorGradientOnCollectionChanged;
|
||||||
ColorGradient.CollectionChanged += ColorGradientOnCollectionChanged;
|
EditingColorGradient.StopChanged += ColorGradientOnStopChanged;
|
||||||
ColorGradient.StopChanged += ColorGradientOnStopChanged;
|
SelectedColorStop = EditingColorGradient.FirstOrDefault();
|
||||||
SelectedColorStop = ColorGradient.FirstOrDefault();
|
|
||||||
|
|
||||||
UpdateGradient();
|
UpdateGradient();
|
||||||
|
|
||||||
_lastColorGradient = ColorGradient;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Unsubscribe()
|
private void ApplyToProperty()
|
||||||
{
|
{
|
||||||
if (_lastColorGradient == null)
|
// Remove extra color gradients
|
||||||
return;
|
while (ColorGradient.Count > EditingColorGradient.Count)
|
||||||
|
ColorGradient.RemoveAt(ColorGradient.Count - 1);
|
||||||
|
|
||||||
_lastColorGradient.CollectionChanged -= ColorGradientOnCollectionChanged;
|
for (int index = 0; index < EditingColorGradient.Count; index++)
|
||||||
_lastColorGradient.StopChanged -= ColorGradientOnStopChanged;
|
{
|
||||||
_lastColorGradient = null;
|
ColorGradientStop colorGradientStop = EditingColorGradient[index];
|
||||||
|
// Add missing color gradients
|
||||||
|
if (index >= ColorGradient.Count)
|
||||||
|
{
|
||||||
|
ColorGradient.Add(new ColorGradientStop(colorGradientStop.Color, colorGradientStop.Position));
|
||||||
|
}
|
||||||
|
// Update existing color gradients
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ColorGradient[index].Color = colorGradientStop.Color;
|
||||||
|
ColorGradient[index].Position = colorGradientStop.Position;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ColorGradientOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
private void ColorGradientOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
UpdateGradient();
|
UpdateGradient();
|
||||||
|
ApplyToProperty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ColorGradientOnStopChanged(object? sender, EventArgs e)
|
private void ColorGradientOnStopChanged(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
UpdateGradient();
|
UpdateGradient();
|
||||||
|
ApplyToProperty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateGradient()
|
private void UpdateGradient()
|
||||||
{
|
{
|
||||||
// Update the display gradient
|
// Update the display gradient
|
||||||
GradientStops collection = new();
|
GradientStops collection = new();
|
||||||
foreach (ColorGradientStop c in ColorGradient.OrderBy(s => s.Position))
|
foreach (ColorGradientStop c in EditingColorGradient.OrderBy(s => s.Position))
|
||||||
collection.Add(new GradientStop(Color.FromArgb(c.Color.Alpha, c.Color.Red, c.Color.Green, c.Color.Blue), c.Position));
|
collection.Add(new GradientStop(Color.FromArgb(c.Color.Alpha, c.Color.Red, c.Color.Green, c.Color.Blue), c.Position));
|
||||||
|
|
||||||
LinearGradientBrush.GradientStops = collection;
|
LinearGradientBrush.GradientStops = collection;
|
||||||
@@ -263,8 +291,8 @@ public class GradientPicker : TemplatedControl
|
|||||||
|
|
||||||
float position = (float) (e.GetPosition(_gradient).X / _gradient.Bounds.Width);
|
float position = (float) (e.GetPosition(_gradient).X / _gradient.Bounds.Width);
|
||||||
|
|
||||||
ColorGradientStop newStop = new(ColorGradient.GetColor(position), position);
|
ColorGradientStop newStop = new(EditingColorGradient.GetColor(position), position);
|
||||||
ColorGradient.Add(newStop);
|
EditingColorGradient.Add(newStop);
|
||||||
SelectedColorStop = newStop;
|
SelectedColorStop = newStop;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,50 +307,50 @@ public class GradientPicker : TemplatedControl
|
|||||||
if (e.Key is Key.LeftShift or Key.RightShift)
|
if (e.Key is Key.LeftShift or Key.RightShift)
|
||||||
_shiftDown = false;
|
_shiftDown = false;
|
||||||
|
|
||||||
if (e.Key != Key.Delete || SelectedColorStop == null || ColorGradient.Count <= 2)
|
if (e.Key != Key.Delete || SelectedColorStop == null || EditingColorGradient.Count <= 2)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int index = ColorGradient.IndexOf(SelectedColorStop);
|
int index = EditingColorGradient.IndexOf(SelectedColorStop);
|
||||||
ColorGradient.Remove(SelectedColorStop);
|
EditingColorGradient.Remove(SelectedColorStop);
|
||||||
if (index > ColorGradient.Count - 1)
|
if (index > EditingColorGradient.Count - 1)
|
||||||
index--;
|
index--;
|
||||||
|
|
||||||
SelectedColorStop = ColorGradient.ElementAtOrDefault(index);
|
SelectedColorStop = EditingColorGradient.ElementAtOrDefault(index);
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SpreadStopsOnClick(object? sender, RoutedEventArgs e)
|
private void SpreadStopsOnClick(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
ColorGradient.SpreadStops();
|
EditingColorGradient.SpreadStops();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ToggleSeamlessOnClick(object? sender, RoutedEventArgs e)
|
private void ToggleSeamlessOnClick(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (SelectedColorStop == null || ColorGradient.Count < 2)
|
if (SelectedColorStop == null || EditingColorGradient.Count < 2)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ColorGradient.ToggleSeamless();
|
EditingColorGradient.ToggleSeamless();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FlipStopsOnClick(object? sender, RoutedEventArgs e)
|
private void FlipStopsOnClick(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (SelectedColorStop == null || ColorGradient.Count < 2)
|
if (SelectedColorStop == null || EditingColorGradient.Count < 2)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ColorGradient.FlipStops();
|
EditingColorGradient.FlipStops();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RotateStopsOnClick(object? sender, RoutedEventArgs e)
|
private void RotateStopsOnClick(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (SelectedColorStop == null || ColorGradient.Count < 2)
|
if (SelectedColorStop == null || EditingColorGradient.Count < 2)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ColorGradient.RotateStops(_shiftDown);
|
EditingColorGradient.RotateStops(_shiftDown);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RandomizeOnClick(object? sender, RoutedEventArgs e)
|
private void RandomizeOnClick(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
ColorGradient.Randomize(6);
|
EditingColorGradient.Randomize(6);
|
||||||
SelectedColorStop = ColorGradient.First();
|
SelectedColorStop = EditingColorGradient.First();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,9 +46,6 @@ public class GradientPickerButton : TemplatedControl
|
|||||||
AvaloniaProperty.RegisterDirect<GradientPickerButton, LinearGradientBrush>(nameof(LinearGradientBrush), g => g.LinearGradientBrush);
|
AvaloniaProperty.RegisterDirect<GradientPickerButton, LinearGradientBrush>(nameof(LinearGradientBrush), g => g.LinearGradientBrush);
|
||||||
|
|
||||||
private Button? _button;
|
private Button? _button;
|
||||||
private GradientPickerFlyout? _flyout;
|
|
||||||
private bool _flyoutActive;
|
|
||||||
|
|
||||||
private ColorGradient? _lastColorGradient;
|
private ColorGradient? _lastColorGradient;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -150,27 +147,23 @@ public class GradientPickerButton : TemplatedControl
|
|||||||
|
|
||||||
private void OnButtonClick(object? sender, RoutedEventArgs e)
|
private void OnButtonClick(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (_flyout == null || ColorGradient == null)
|
if (ColorGradient == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Logic here is taken from Fluent Avalonia's ColorPicker which also reuses the same control since it's large
|
GradientPickerFlyout flyout = new();
|
||||||
_flyout.GradientPicker.ColorGradient = ColorGradient;
|
flyout.FlyoutPresenterClasses.Add("gradient-picker-presenter");
|
||||||
_flyout.GradientPicker.IsCompact = IsCompact;
|
flyout.GradientPicker.ColorGradient = ColorGradient;
|
||||||
_flyout.GradientPicker.StorageProvider = StorageProvider;
|
flyout.GradientPicker.IsCompact = IsCompact;
|
||||||
|
flyout.GradientPicker.StorageProvider = StorageProvider;
|
||||||
_flyout.ShowAt(this);
|
|
||||||
_flyoutActive = true;
|
|
||||||
|
|
||||||
|
flyout.Closed += FlyoutOnClosed;
|
||||||
|
flyout.ShowAt(this);
|
||||||
FlyoutOpened?.Invoke(this, EventArgs.Empty);
|
FlyoutOpened?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
|
||||||
|
|
||||||
|
void FlyoutOnClosed(object? closedSender, EventArgs closedEventArgs)
|
||||||
private void OnFlyoutClosed(object? sender, EventArgs e)
|
|
||||||
{
|
|
||||||
if (_flyoutActive)
|
|
||||||
{
|
{
|
||||||
|
flyout.Closed -= FlyoutOnClosed;
|
||||||
FlyoutClosed?.Invoke(this, EventArgs.Empty);
|
FlyoutClosed?.Invoke(this, EventArgs.Empty);
|
||||||
_flyoutActive = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,22 +184,12 @@ public class GradientPickerButton : TemplatedControl
|
|||||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||||
{
|
{
|
||||||
Subscribe();
|
Subscribe();
|
||||||
|
|
||||||
if (_flyout == null)
|
|
||||||
{
|
|
||||||
_flyout = new GradientPickerFlyout();
|
|
||||||
_flyout.FlyoutPresenterClasses.Add("gradient-picker-presenter");
|
|
||||||
}
|
|
||||||
|
|
||||||
_flyout.Closed += OnFlyoutClosed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||||
{
|
{
|
||||||
Unsubscribe();
|
Unsubscribe();
|
||||||
if (_flyout != null)
|
|
||||||
_flyout.Closed -= OnFlyoutClosed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ public class UpdateColorGradient : IProfileEditorCommand
|
|||||||
public UpdateColorGradient(ColorGradient colorGradient, List<ColorGradientStop> stops, List<ColorGradientStop>? originalStops)
|
public UpdateColorGradient(ColorGradient colorGradient, List<ColorGradientStop> stops, List<ColorGradientStop>? originalStops)
|
||||||
{
|
{
|
||||||
_colorGradient = colorGradient;
|
_colorGradient = colorGradient;
|
||||||
_stops = stops;
|
_stops = stops.Select(s => new ColorGradientStop(s.Color, s.Position)).ToList();
|
||||||
_originalStops = originalStops ?? _colorGradient.Select(s => new ColorGradientStop(s.Color, s.Position)).ToList();
|
_originalStops = originalStops ?? _colorGradient.Select(s => new ColorGradientStop(s.Color, s.Position)).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,18 +31,36 @@ public class UpdateColorGradient : IProfileEditorCommand
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Execute()
|
public void Execute()
|
||||||
{
|
{
|
||||||
_colorGradient.Clear();
|
ApplyStops(_stops);
|
||||||
foreach (ColorGradientStop colorGradientStop in _stops)
|
|
||||||
_colorGradient.Add(colorGradientStop);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Undo()
|
public void Undo()
|
||||||
{
|
{
|
||||||
_colorGradient.Clear();
|
ApplyStops(_originalStops);
|
||||||
foreach (ColorGradientStop colorGradientStop in _originalStops)
|
|
||||||
_colorGradient.Add(colorGradientStop);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
private void ApplyStops(List<ColorGradientStop> stops)
|
||||||
|
{
|
||||||
|
while (_colorGradient.Count > stops.Count)
|
||||||
|
_colorGradient.RemoveAt(_colorGradient.Count - 1);
|
||||||
|
|
||||||
|
for (int index = 0; index < stops.Count; index++)
|
||||||
|
{
|
||||||
|
ColorGradientStop colorGradientStop = stops[index];
|
||||||
|
// Add missing color gradients
|
||||||
|
if (index >= _colorGradient.Count)
|
||||||
|
{
|
||||||
|
_colorGradient.Add(new ColorGradientStop(colorGradientStop.Color, colorGradientStop.Position));
|
||||||
|
}
|
||||||
|
// Update existing color gradients
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_colorGradient[index].Color = colorGradientStop.Color;
|
||||||
|
_colorGradient[index].Position = colorGradientStop.Position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -73,7 +73,6 @@
|
|||||||
|
|
||||||
<Style Selector="gradientPicker|GradientPicker">
|
<Style Selector="gradientPicker|GradientPicker">
|
||||||
<Style.Resources>
|
<Style.Resources>
|
||||||
<converters:ColorGradientToGradientStopsConverter x:Key="ColorGradientToGradientStopsConverter" />
|
|
||||||
<converters:SKColorToColorConverter x:Key="SKColorToColorConverter" />
|
<converters:SKColorToColorConverter x:Key="SKColorToColorConverter" />
|
||||||
<converters:SKColorToBrushConverter x:Key="SKColorToBrushConverter" />
|
<converters:SKColorToBrushConverter x:Key="SKColorToBrushConverter" />
|
||||||
<converters:SKColorToStringConverter x:Key="SKColorToStringConverter" />
|
<converters:SKColorToStringConverter x:Key="SKColorToStringConverter" />
|
||||||
@@ -89,7 +88,7 @@
|
|||||||
Background="{DynamicResource LightCheckerboardBrush}"
|
Background="{DynamicResource LightCheckerboardBrush}"
|
||||||
Margin="5 0">
|
Margin="5 0">
|
||||||
<Border Background="{TemplateBinding LinearGradientBrush}">
|
<Border Background="{TemplateBinding LinearGradientBrush}">
|
||||||
<ItemsControl Name="GradientStops" Items="{TemplateBinding ColorGradient}" ClipToBounds="False">
|
<ItemsControl Name="GradientStops" Items="{TemplateBinding EditingColorGradient}" ClipToBounds="False">
|
||||||
<ItemsControl.Styles>
|
<ItemsControl.Styles>
|
||||||
<Style Selector="ItemsControl#GradientStops > ContentPresenter">
|
<Style Selector="ItemsControl#GradientStops > ContentPresenter">
|
||||||
<Setter Property="Canvas.Left">
|
<Setter Property="Canvas.Left">
|
||||||
@@ -135,7 +134,7 @@
|
|||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.ColumnSpan="2"
|
Grid.ColumnSpan="2"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Items="{TemplateBinding ColorGradient}"
|
Items="{TemplateBinding EditingColorGradient}"
|
||||||
ClipToBounds="False"
|
ClipToBounds="False"
|
||||||
Margin="5 0">
|
Margin="5 0">
|
||||||
<ItemsControl.Styles>
|
<ItemsControl.Styles>
|
||||||
@@ -180,9 +179,10 @@
|
|||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<Grid Grid.Row="2" Grid.Column="1" RowDefinitions="*,Auto">
|
<Grid Grid.Row="2" Grid.Column="1" RowDefinitions="*,Auto">
|
||||||
<ListBox Grid.Row="0"
|
<ListBox Name="GradientColors"
|
||||||
|
Grid.Row="0"
|
||||||
MaxHeight="280"
|
MaxHeight="280"
|
||||||
Items="{TemplateBinding ColorGradient}"
|
Items="{TemplateBinding EditingColorGradient}"
|
||||||
SelectedItem="{TemplateBinding SelectedColorStop, Mode=TwoWay}"
|
SelectedItem="{TemplateBinding SelectedColorStop, Mode=TwoWay}"
|
||||||
Padding="10 0 15 0">
|
Padding="10 0 15 0">
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
@@ -201,7 +201,7 @@
|
|||||||
<TextBox Grid.Column="1" Text="{Binding Color, Converter={StaticResource SKColorToStringConverter}}" />
|
<TextBox Grid.Column="1" Text="{Binding Color, Converter={StaticResource SKColorToStringConverter}}" />
|
||||||
<NumericUpDown Grid.Column="2" FormatString="F3" ShowButtonSpinner="False" Margin="5 0" Minimum="0" Maximum="1" Increment="0.01">
|
<NumericUpDown Grid.Column="2" FormatString="F3" ShowButtonSpinner="False" Margin="5 0" Minimum="0" Maximum="1" Increment="0.01">
|
||||||
<Interaction.Behaviors>
|
<Interaction.Behaviors>
|
||||||
<behaviors:LostFocusNumericUpDownBindingBehavior Value="{Binding Position}"/>
|
<behaviors:LostFocusNumericUpDownBindingBehavior Value="{Binding Position}" />
|
||||||
</Interaction.Behaviors>
|
</Interaction.Behaviors>
|
||||||
</NumericUpDown>
|
</NumericUpDown>
|
||||||
<Button Name="DeleteButton"
|
<Button Name="DeleteButton"
|
||||||
|
|||||||
@@ -25,9 +25,9 @@
|
|||||||
</Grid.ContextFlyout>
|
</Grid.ContextFlyout>
|
||||||
|
|
||||||
<!-- Icon column -->
|
<!-- Icon column -->
|
||||||
<shared:ArtemisIcon Grid.Column="0"
|
<avalonia:MaterialIcon Grid.Column="0"
|
||||||
Icon="{CompiledBinding FeatureInfo.ResolvedIcon}"
|
ToolTip.Tip="{CompiledBinding FeatureType}"
|
||||||
Fill="False"
|
Kind="{CompiledBinding FeatureIcon}"
|
||||||
Width="20"
|
Width="20"
|
||||||
Height="20"
|
Height="20"
|
||||||
IsVisible="{CompiledBinding LoadException, Converter={x:Static ObjectConverters.IsNull}}" />
|
IsVisible="{CompiledBinding LoadException, Converter={x:Static ObjectConverters.IsNull}}" />
|
||||||
|
|||||||
@@ -5,11 +5,17 @@ using System.Reactive;
|
|||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
|
using Artemis.Core.DeviceProviders;
|
||||||
|
using Artemis.Core.LayerBrushes;
|
||||||
|
using Artemis.Core.LayerEffects;
|
||||||
|
using Artemis.Core.Modules;
|
||||||
|
using Artemis.Core.ScriptingProviders;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
using Artemis.UI.Shared.Services.Builders;
|
using Artemis.UI.Shared.Services.Builders;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
|
using Material.Icons;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Plugins;
|
namespace Artemis.UI.Screens.Plugins;
|
||||||
@@ -90,6 +96,42 @@ public class PluginFeatureViewModel : ActivatableViewModelBase
|
|||||||
public bool CanRemovePrerequisites => FeatureInfo.PlatformPrerequisites.Any(p => p.UninstallActions.Any());
|
public bool CanRemovePrerequisites => FeatureInfo.PlatformPrerequisites.Any(p => p.UninstallActions.Any());
|
||||||
public bool IsPopupEnabled => CanInstallPrerequisites || CanRemovePrerequisites;
|
public bool IsPopupEnabled => CanInstallPrerequisites || CanRemovePrerequisites;
|
||||||
|
|
||||||
|
public MaterialIconKind FeatureIcon
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (FeatureInfo.FeatureType.IsAssignableTo(typeof(DeviceProvider)))
|
||||||
|
return MaterialIconKind.Devices;
|
||||||
|
if (FeatureInfo.FeatureType.IsAssignableTo(typeof(Module)))
|
||||||
|
return MaterialIconKind.VectorRectangle;
|
||||||
|
if (FeatureInfo.FeatureType.IsAssignableTo(typeof(LayerBrushProvider)))
|
||||||
|
return MaterialIconKind.Brush;
|
||||||
|
if (FeatureInfo.FeatureType.IsAssignableTo(typeof(LayerEffectProvider)))
|
||||||
|
return MaterialIconKind.AutoAwesome;
|
||||||
|
if (FeatureInfo.FeatureType.IsAssignableTo(typeof(ScriptingProvider)))
|
||||||
|
return MaterialIconKind.Code;
|
||||||
|
return MaterialIconKind.Extension;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string FeatureType
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (FeatureInfo.FeatureType.IsAssignableTo(typeof(DeviceProvider)))
|
||||||
|
return "Device Provider";
|
||||||
|
if (FeatureInfo.FeatureType.IsAssignableTo(typeof(Module)))
|
||||||
|
return "Module";
|
||||||
|
if (FeatureInfo.FeatureType.IsAssignableTo(typeof(LayerBrushProvider)))
|
||||||
|
return "Layer Brush";
|
||||||
|
if (FeatureInfo.FeatureType.IsAssignableTo(typeof(LayerEffectProvider)))
|
||||||
|
return "Layer Effect";
|
||||||
|
if (FeatureInfo.FeatureType.IsAssignableTo(typeof(ScriptingProvider)))
|
||||||
|
return "Scripting Provider";
|
||||||
|
return "Miscellaneous feature";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void ExecuteShowLogsFolder()
|
private void ExecuteShowLogsFolder()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -29,7 +29,22 @@ public class PlaybackViewModel : ActivatableViewModelBase
|
|||||||
{
|
{
|
||||||
_profileEditorService = profileEditorService;
|
_profileEditorService = profileEditorService;
|
||||||
_settingsService = settingsService;
|
_settingsService = settingsService;
|
||||||
|
|
||||||
|
if (_settingsService.GetSetting("ProfileEditor.RepeatTimeline", true).Value)
|
||||||
|
{
|
||||||
|
_repeating = true;
|
||||||
_repeatTimeline = true;
|
_repeatTimeline = true;
|
||||||
|
}
|
||||||
|
else if (_settingsService.GetSetting("ProfileEditor.RepeatSegment", false).Value)
|
||||||
|
{
|
||||||
|
_repeating = true;
|
||||||
|
_repeatSegment = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_repeating = false;
|
||||||
|
_repeatTimeline = true;
|
||||||
|
}
|
||||||
|
|
||||||
this.WhenActivated(d =>
|
this.WhenActivated(d =>
|
||||||
{
|
{
|
||||||
@@ -42,7 +57,12 @@ public class PlaybackViewModel : ActivatableViewModelBase
|
|||||||
_lastUpdate = DateTime.MinValue;
|
_lastUpdate = DateTime.MinValue;
|
||||||
DispatcherTimer updateTimer = new(TimeSpan.FromMilliseconds(60.0 / 1000), DispatcherPriority.Render, Update);
|
DispatcherTimer updateTimer = new(TimeSpan.FromMilliseconds(60.0 / 1000), DispatcherPriority.Render, Update);
|
||||||
updateTimer.Start();
|
updateTimer.Start();
|
||||||
Disposable.Create(() => updateTimer.Stop()).DisposeWith(d);
|
Disposable.Create(() =>
|
||||||
|
{
|
||||||
|
updateTimer.Stop();
|
||||||
|
_settingsService.GetSetting("ProfileEditor.RepeatTimeline", true).Value = _repeating && _repeatTimeline;
|
||||||
|
_settingsService.GetSetting("ProfileEditor.RepeatSegment", false).Value = _repeating && _repeatSegment;
|
||||||
|
}).DisposeWith(d);
|
||||||
});
|
});
|
||||||
|
|
||||||
PlayFromStart = ReactiveCommand.Create(ExecutePlayFromStart, this.WhenAnyValue(vm => vm.KeyBindingsEnabled));
|
PlayFromStart = ReactiveCommand.Create(ExecutePlayFromStart, this.WhenAnyValue(vm => vm.KeyBindingsEnabled));
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<ComboBox.ItemTemplate>
|
<ComboBox.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
<shared:ArtemisIcon Icon="{CompiledBinding Info.ResolvedIcon}" Width="16" Height="16" Margin="0 0 5 0" />
|
<shared:ArtemisIcon Icon="{CompiledBinding Plugin.Info.ResolvedIcon}" Width="16" Height="16" Margin="0 0 5 0" />
|
||||||
<TextBlock Text="{CompiledBinding LanguageName}" />
|
<TextBlock Text="{CompiledBinding LanguageName}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
<shared:ArtemisIcon Grid.Row="0"
|
<shared:ArtemisIcon Grid.Row="0"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
Icon="{CompiledBinding ScriptConfiguration.Script.ScriptingProvider.Info.ResolvedIcon, FallbackValue=QuestionMark}"
|
Icon="{CompiledBinding ScriptConfiguration.Script.ScriptingProvider.Plugin.Info.ResolvedIcon, FallbackValue=QuestionMark}"
|
||||||
Width="32 "
|
Width="32 "
|
||||||
Height="32"
|
Height="32"
|
||||||
Margin="0 0 10 0"
|
Margin="0 0 10 0"
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ public class ProfileModuleViewModel : ViewModelBase
|
|||||||
{
|
{
|
||||||
Module = module;
|
Module = module;
|
||||||
Name = module.Info.Name;
|
Name = module.Info.Name;
|
||||||
Icon = module.Info.ResolvedIcon ?? MaterialIconKind.QuestionMark.ToString();
|
Icon = module.Plugin.Info.ResolvedIcon ?? MaterialIconKind.QuestionMark.ToString();
|
||||||
Description = module.Info.Description;
|
Description = module.Info.Description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,10 +55,10 @@
|
|||||||
<DataTemplate DataType="skiaSharp:SKColor">
|
<DataTemplate DataType="skiaSharp:SKColor">
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
||||||
<TextBlock x:Name="HexDisplay"
|
<TextBlock x:Name="HexDisplay"
|
||||||
Text="{CompiledBinding Converter={StaticResource SKColorToStringConverter}}"
|
Text="{CompiledBinding Converter={StaticResource SKColorToStringConverter}, Mode=OneWay}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
FontFamily="Consolas"/>
|
FontFamily="Consolas" />
|
||||||
<Border Margin="5 0 0 0"
|
<Border Margin="5 0 0 0"
|
||||||
VerticalAlignment="Bottom"
|
VerticalAlignment="Bottom"
|
||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
@@ -71,20 +71,23 @@
|
|||||||
ClipToBounds="True">
|
ClipToBounds="True">
|
||||||
<Border CornerRadius="4">
|
<Border CornerRadius="4">
|
||||||
<Border.Background>
|
<Border.Background>
|
||||||
<SolidColorBrush Color="{Binding Converter={StaticResource SKColorToColorConverter}}" />
|
<SolidColorBrush Color="{Binding Converter={StaticResource SKColorToColorConverter}, Mode=OneWay}" />
|
||||||
</Border.Background>
|
</Border.Background>
|
||||||
</Border>
|
</Border>
|
||||||
</Border>
|
</Border>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
<DataTemplate DataType="core:ColorGradient">
|
||||||
|
<TextBlock Text="Color gradient" FontFamily="Consolas" />
|
||||||
|
</DataTemplate>
|
||||||
<DataTemplate DataType="core:Numeric">
|
<DataTemplate DataType="core:Numeric">
|
||||||
<TextBlock Text="{Binding}" FontFamily="Consolas"/>
|
<TextBlock Text="{Binding Mode=OneWay}" FontFamily="Consolas" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
<DataTemplate DataType="collections:IList">
|
<DataTemplate DataType="collections:IList">
|
||||||
<TextBlock Text="{Binding Count, StringFormat='List - {0} item(s)'}" FontFamily="Consolas"/>
|
<TextBlock Text="{Binding Count, StringFormat='List - {0} item(s)', Mode=OneWay}" FontFamily="Consolas" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<TextBlock Text="{Binding}" FontFamily="Consolas"/>
|
<TextBlock Text="{Binding Mode=OneWay}" FontFamily="Consolas" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ContentControl.DataTemplates>
|
</ContentControl.DataTemplates>
|
||||||
</ContentControl>
|
</ContentControl>
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
<avalonia:MaterialIcon Kind="AlertCircle"></avalonia:MaterialIcon>
|
<avalonia:MaterialIcon Kind="AlertCircle"></avalonia:MaterialIcon>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="10 0 0 0" Text="{CompiledBinding Node.Name}" ToolTip.Tip="{CompiledBinding Node.Description}"/>
|
<TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="10 0 0 0" Text="{CompiledBinding Node.Name}" ToolTip.Tip="{CompiledBinding Node.Description}" />
|
||||||
|
|
||||||
<Button Grid.Column="2" VerticalAlignment="Center" Classes="icon-button icon-button-small" Margin="5" Command="{CompiledBinding DeleteNode}">
|
<Button Grid.Column="2" VerticalAlignment="Center" Classes="icon-button icon-button-small" Margin="5" Command="{CompiledBinding DeleteNode}">
|
||||||
<avalonia:MaterialIcon Kind="Close"></avalonia:MaterialIcon>
|
<avalonia:MaterialIcon Kind="Close"></avalonia:MaterialIcon>
|
||||||
@@ -60,21 +60,36 @@
|
|||||||
</Border>
|
</Border>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<Grid Grid.Row="1" ColumnDefinitions="Auto,*,Auto" Margin="4">
|
<Grid Grid.Row="1" RowDefinitions="Auto,Auto,Auto" ColumnDefinitions="Auto,*,Auto" Margin="4">
|
||||||
<StackPanel Grid.Column="0" IsVisible="{CompiledBinding HasInputPins}">
|
<ContentControl Grid.Row="0"
|
||||||
|
Grid.Column="0"
|
||||||
|
Grid.ColumnSpan="3"
|
||||||
|
Margin="5"
|
||||||
|
Content="{CompiledBinding CustomNodeViewModel}"
|
||||||
|
IsVisible="{CompiledBinding DisplayCustomViewModelAbove}" />
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="1" Grid.Column="0" IsVisible="{CompiledBinding HasInputPins}">
|
||||||
<ItemsControl Items="{CompiledBinding InputPinViewModels}" Margin="4 0" />
|
<ItemsControl Items="{CompiledBinding InputPinViewModels}" Margin="4 0" />
|
||||||
<ItemsControl Items="{CompiledBinding InputPinCollectionViewModels}" />
|
<ItemsControl Items="{CompiledBinding InputPinCollectionViewModels}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<ContentControl Grid.Column="1"
|
<ContentControl Grid.Row="1"
|
||||||
Name="CustomViewModelContainer"
|
Grid.Column="1"
|
||||||
Content="{CompiledBinding CustomNodeViewModel}"
|
Content="{CompiledBinding CustomNodeViewModel}"
|
||||||
IsVisible="{CompiledBinding CustomNodeViewModel, Converter={x:Static ObjectConverters.IsNotNull}}" />
|
VerticalAlignment="{CompiledBinding CustomViewModelVerticalAlignment}"
|
||||||
|
IsVisible="{CompiledBinding DisplayCustomViewModelBetween}" />
|
||||||
|
|
||||||
<StackPanel Grid.Column="2" IsVisible="{CompiledBinding HasOutputPins}">
|
<StackPanel Grid.Row="1" Grid.Column="2" IsVisible="{CompiledBinding HasOutputPins}">
|
||||||
<ItemsControl Items="{CompiledBinding OutputPinViewModels}" Margin="4 0" />
|
<ItemsControl Items="{CompiledBinding OutputPinViewModels}" Margin="4 0" />
|
||||||
<ItemsControl Items="{CompiledBinding OutputPinCollectionViewModels}" />
|
<ItemsControl Items="{CompiledBinding OutputPinCollectionViewModels}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
<ContentControl Grid.Row="2"
|
||||||
|
Grid.Column="0"
|
||||||
|
Grid.ColumnSpan="3"
|
||||||
|
Margin="5"
|
||||||
|
Content="{CompiledBinding CustomNodeViewModel}"
|
||||||
|
IsVisible="{CompiledBinding DisplayCustomViewModelBelow}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ using Artemis.UI.Shared.Services.NodeEditor;
|
|||||||
using Artemis.UI.Shared.Services.NodeEditor.Commands;
|
using Artemis.UI.Shared.Services.NodeEditor.Commands;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls.Mixins;
|
using Avalonia.Controls.Mixins;
|
||||||
|
using Avalonia.Layout;
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
using DynamicData.Binding;
|
using DynamicData.Binding;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
@@ -34,6 +35,10 @@ public class NodeViewModel : ActivatableViewModelBase
|
|||||||
private ObservableAsPropertyHelper<bool>? _isStaticNode;
|
private ObservableAsPropertyHelper<bool>? _isStaticNode;
|
||||||
private double _startX;
|
private double _startX;
|
||||||
private double _startY;
|
private double _startY;
|
||||||
|
private bool _displayCustomViewModelAbove;
|
||||||
|
private bool _displayCustomViewModelBetween;
|
||||||
|
private bool _displayCustomViewModelBelow;
|
||||||
|
private VerticalAlignment _customViewModelVerticalAlignment;
|
||||||
|
|
||||||
public NodeViewModel(NodeScriptViewModel nodeScriptViewModel, INode node, INodeVmFactory nodeVmFactory, INodeEditorService nodeEditorService, IWindowService windowService)
|
public NodeViewModel(NodeScriptViewModel nodeScriptViewModel, INode node, INodeVmFactory nodeVmFactory, INodeEditorService nodeEditorService, IWindowService windowService)
|
||||||
{
|
{
|
||||||
@@ -132,15 +137,32 @@ public class NodeViewModel : ActivatableViewModelBase
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Node is Node coreNode)
|
// Set up the custom node VM if needed
|
||||||
CustomNodeViewModel = coreNode.GetCustomViewModel(nodeScriptViewModel.NodeScript);
|
if (Node is ICustomViewModelNode customViewModelNode)
|
||||||
|
{
|
||||||
|
CustomNodeViewModel = customViewModelNode.GetCustomViewModel(nodeScriptViewModel.NodeScript);
|
||||||
|
if (customViewModelNode.ViewModelPosition == CustomNodeViewModelPosition.AbovePins)
|
||||||
|
DisplayCustomViewModelAbove = true;
|
||||||
|
else if (customViewModelNode.ViewModelPosition == CustomNodeViewModelPosition.BelowPins)
|
||||||
|
DisplayCustomViewModelBelow = true;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DisplayCustomViewModelBetween = true;
|
||||||
|
|
||||||
|
if (customViewModelNode.ViewModelPosition == CustomNodeViewModelPosition.BetweenPinsTop)
|
||||||
|
CustomViewModelVerticalAlignment = VerticalAlignment.Top;
|
||||||
|
else if (customViewModelNode.ViewModelPosition == CustomNodeViewModelPosition.BetweenPinsTop)
|
||||||
|
CustomViewModelVerticalAlignment = VerticalAlignment.Center;
|
||||||
|
else
|
||||||
|
CustomViewModelVerticalAlignment = VerticalAlignment.Bottom;
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsStaticNode => _isStaticNode?.Value ?? true;
|
public bool IsStaticNode => _isStaticNode?.Value ?? true;
|
||||||
public bool HasInputPins => _hasInputPins?.Value ?? false;
|
public bool HasInputPins => _hasInputPins?.Value ?? false;
|
||||||
public bool HasOutputPins => _hasOutputPins?.Value ?? false;
|
public bool HasOutputPins => _hasOutputPins?.Value ?? false;
|
||||||
|
|
||||||
public NodeScriptViewModel NodeScriptViewModel { get; }
|
public NodeScriptViewModel NodeScriptViewModel { get; }
|
||||||
public INode Node { get; }
|
public INode Node { get; }
|
||||||
public ReadOnlyObservableCollection<PinViewModel> InputPinViewModels { get; }
|
public ReadOnlyObservableCollection<PinViewModel> InputPinViewModels { get; }
|
||||||
@@ -149,16 +171,40 @@ public class NodeViewModel : ActivatableViewModelBase
|
|||||||
public ReadOnlyObservableCollection<PinCollectionViewModel> OutputPinCollectionViewModels { get; }
|
public ReadOnlyObservableCollection<PinCollectionViewModel> OutputPinCollectionViewModels { get; }
|
||||||
public ReadOnlyObservableCollection<PinViewModel> PinViewModels { get; }
|
public ReadOnlyObservableCollection<PinViewModel> PinViewModels { get; }
|
||||||
|
|
||||||
|
public bool IsSelected
|
||||||
|
{
|
||||||
|
get => _isSelected;
|
||||||
|
set => RaiseAndSetIfChanged(ref _isSelected, value);
|
||||||
|
}
|
||||||
|
|
||||||
public ICustomNodeViewModel? CustomNodeViewModel
|
public ICustomNodeViewModel? CustomNodeViewModel
|
||||||
{
|
{
|
||||||
get => _customNodeViewModel;
|
get => _customNodeViewModel;
|
||||||
set => RaiseAndSetIfChanged(ref _customNodeViewModel, value);
|
set => RaiseAndSetIfChanged(ref _customNodeViewModel, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsSelected
|
public bool DisplayCustomViewModelAbove
|
||||||
{
|
{
|
||||||
get => _isSelected;
|
get => _displayCustomViewModelAbove;
|
||||||
set => RaiseAndSetIfChanged(ref _isSelected, value);
|
set => RaiseAndSetIfChanged(ref _displayCustomViewModelAbove, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DisplayCustomViewModelBetween
|
||||||
|
{
|
||||||
|
get => _displayCustomViewModelBetween;
|
||||||
|
set => RaiseAndSetIfChanged(ref _displayCustomViewModelBetween, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DisplayCustomViewModelBelow
|
||||||
|
{
|
||||||
|
get => _displayCustomViewModelBelow;
|
||||||
|
set => RaiseAndSetIfChanged(ref _displayCustomViewModelBelow, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public VerticalAlignment CustomViewModelVerticalAlignment
|
||||||
|
{
|
||||||
|
get => _customViewModelVerticalAlignment;
|
||||||
|
set => RaiseAndSetIfChanged(ref _customViewModelVerticalAlignment, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> ShowBrokenState { get; }
|
public ReactiveCommand<Unit, Unit> ShowBrokenState { get; }
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ public class RegistrationService : IRegistrationService
|
|||||||
_nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(SKColor), new SKColor(0xFFAD3EED));
|
_nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(SKColor), new SKColor(0xFFAD3EED));
|
||||||
_nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(IList), new SKColor(0xFFED3E61));
|
_nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(IList), new SKColor(0xFFED3E61));
|
||||||
_nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(Enum), new SKColor(0xFF1E90FF));
|
_nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(Enum), new SKColor(0xFF1E90FF));
|
||||||
|
_nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(ColorGradient), new SKColor(0xFF00B2A9));
|
||||||
|
|
||||||
foreach (Type nodeType in typeof(SumNumericsNode).Assembly.GetTypes().Where(t => typeof(INode).IsAssignableFrom(t) && t.IsPublic && !t.IsAbstract && !t.IsInterface))
|
foreach (Type nodeType in typeof(SumNumericsNode).Assembly.GetTypes().Where(t => typeof(INode).IsAssignableFrom(t) && t.IsPublic && !t.IsAbstract && !t.IsInterface))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -37,6 +37,9 @@
|
|||||||
<DependentUpon>DataModelEventNodeCustomView.axaml</DependentUpon>
|
<DependentUpon>DataModelEventNodeCustomView.axaml</DependentUpon>
|
||||||
<SubType>Code</SubType>
|
<SubType>Code</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Update="Nodes\Color\Screens\ColorGradientNodeCustomView.axaml.cs">
|
||||||
|
<DependentUpon>ColorGradientNodeCustomView.axaml</DependentUpon>
|
||||||
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.Core.Events;
|
||||||
|
using SkiaSharp;
|
||||||
|
|
||||||
|
namespace Artemis.VisualScripting.Nodes.Color;
|
||||||
|
|
||||||
|
[Node("Color Gradient (Advanced)", "Outputs a Color Gradient from colors and positions", "Color", OutputType = typeof(ColorGradient))]
|
||||||
|
public class ColorGradientFromPinsNode : Node
|
||||||
|
{
|
||||||
|
public OutputPin<ColorGradient> Gradient { get; set; }
|
||||||
|
public InputPinCollection<SKColor> Colors { get; set; }
|
||||||
|
public InputPinCollection<Numeric> Positions { get; set; }
|
||||||
|
|
||||||
|
public ColorGradientFromPinsNode() : base("Color Gradient", "Outputs a Color Gradient from colors and positions")
|
||||||
|
{
|
||||||
|
Colors = CreateInputPinCollection<SKColor>("Colors", 0);
|
||||||
|
Positions = CreateInputPinCollection<Numeric>("Positions", 0);
|
||||||
|
Gradient = CreateOutputPin<ColorGradient>("Gradient");
|
||||||
|
|
||||||
|
Colors.PinAdded += OnPinAdded;
|
||||||
|
Colors.PinRemoved += OnPinRemoved;
|
||||||
|
Positions.PinAdded += OnPinAdded;
|
||||||
|
Positions.PinRemoved += OnPinRemoved;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPinRemoved(object? sender, SingleValueEventArgs<IPin> e)
|
||||||
|
{
|
||||||
|
int colorsCount = Colors.Count();
|
||||||
|
int positionsCount = Positions.Count();
|
||||||
|
if (colorsCount == positionsCount)
|
||||||
|
return;
|
||||||
|
|
||||||
|
while (colorsCount > positionsCount)
|
||||||
|
{
|
||||||
|
IPin pinToRemove = Colors.Last();
|
||||||
|
Colors.Remove(pinToRemove);
|
||||||
|
|
||||||
|
--colorsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (positionsCount > colorsCount)
|
||||||
|
{
|
||||||
|
IPin pinToRemove = Positions.Last();
|
||||||
|
Positions.Remove(pinToRemove);
|
||||||
|
--positionsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenamePins();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPinAdded(object? sender, SingleValueEventArgs<IPin> e)
|
||||||
|
{
|
||||||
|
int colorsCount = Colors.Count();
|
||||||
|
int positionsCount = Positions.Count();
|
||||||
|
if (colorsCount == positionsCount)
|
||||||
|
return;
|
||||||
|
|
||||||
|
while (colorsCount < positionsCount)
|
||||||
|
{
|
||||||
|
Colors.Add(Colors.CreatePin());
|
||||||
|
|
||||||
|
++colorsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (positionsCount < colorsCount)
|
||||||
|
{
|
||||||
|
Positions.Add(Positions.CreatePin());
|
||||||
|
|
||||||
|
++positionsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenamePins();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenamePins()
|
||||||
|
{
|
||||||
|
int colors = 0;
|
||||||
|
foreach (IPin item in Colors)
|
||||||
|
{
|
||||||
|
item.Name = $"Color #{++colors}";
|
||||||
|
}
|
||||||
|
|
||||||
|
int positions = 0;
|
||||||
|
foreach (IPin item in Positions)
|
||||||
|
{
|
||||||
|
item.Name = $"Position #{++positions}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Evaluate()
|
||||||
|
{
|
||||||
|
List<ColorGradientStop> stops = new List<ColorGradientStop>();
|
||||||
|
InputPin<SKColor>[] colors = Colors.Pins.ToArray();
|
||||||
|
InputPin<Numeric>[] positions = Positions.Pins.ToArray();
|
||||||
|
for (int i = 0; i < colors.Length; i++)
|
||||||
|
{
|
||||||
|
stops.Add(new ColorGradientStop(colors[i].Value, positions[i].Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
Gradient.Value = new ColorGradient(stops);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
using System.Collections.Specialized;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.VisualScripting.Nodes.Color.Screens;
|
||||||
|
using SkiaSharp;
|
||||||
|
|
||||||
|
namespace Artemis.VisualScripting.Nodes.Color;
|
||||||
|
|
||||||
|
[Node("Color Gradient (Simple)", "Outputs a color gradient with the given colors", "Color", OutputType = typeof(ColorGradient))]
|
||||||
|
public class ColorGradientNode : Node<ColorGradient, ColorGradientNodeCustomViewModel>
|
||||||
|
{
|
||||||
|
private readonly List<InputPin> _inputPins;
|
||||||
|
|
||||||
|
public ColorGradientNode() : base("Color Gradient", "Outputs a color gradient with the given colors")
|
||||||
|
{
|
||||||
|
_inputPins = new List<InputPin>();
|
||||||
|
|
||||||
|
Gradient = ColorGradient.GetUnicornBarf();
|
||||||
|
Output = CreateOutputPin<ColorGradient>();
|
||||||
|
ViewModelPosition = CustomNodeViewModelPosition.AbovePins;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ColorGradient Gradient { get; private set; }
|
||||||
|
public OutputPin<ColorGradient> Output { get; }
|
||||||
|
|
||||||
|
public override void Initialize(INodeScript script)
|
||||||
|
{
|
||||||
|
UpdateGradient();
|
||||||
|
ComputeInputPins();
|
||||||
|
|
||||||
|
// Not expecting storage to get modified, but lets just make sure
|
||||||
|
StorageModified += OnStorageModified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Evaluate()
|
||||||
|
{
|
||||||
|
ColorGradientStop[] stops = Gradient.ToArray();
|
||||||
|
|
||||||
|
if (_inputPins.Count != stops.Length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (int i = 0; i < _inputPins.Count; i++)
|
||||||
|
{
|
||||||
|
// if nothing is connected, leave the stop alone.
|
||||||
|
if (_inputPins[i].ConnectedTo.Count == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// if the pin has a connection, update the stop.
|
||||||
|
if (_inputPins[i].PinValue is SKColor color)
|
||||||
|
stops[i].Color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
Output.Value = Gradient;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisconnectAllInputPins()
|
||||||
|
{
|
||||||
|
foreach (InputPin item in _inputPins)
|
||||||
|
item.DisconnectAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateGradient()
|
||||||
|
{
|
||||||
|
Gradient.CollectionChanged -= OnGradientCollectionChanged;
|
||||||
|
if (Storage != null)
|
||||||
|
Gradient = Storage;
|
||||||
|
else
|
||||||
|
Storage = Gradient;
|
||||||
|
Gradient.CollectionChanged += OnGradientCollectionChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ComputeInputPins()
|
||||||
|
{
|
||||||
|
int newAmount = Gradient.Count;
|
||||||
|
if (newAmount == _inputPins.Count)
|
||||||
|
return;
|
||||||
|
|
||||||
|
while (newAmount > _inputPins.Count)
|
||||||
|
_inputPins.Add(CreateOrAddInputPin(typeof(SKColor), string.Empty));
|
||||||
|
|
||||||
|
while (newAmount < _inputPins.Count)
|
||||||
|
{
|
||||||
|
InputPin pin = _inputPins.Last();
|
||||||
|
RemovePin(pin);
|
||||||
|
_inputPins.Remove(pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
foreach (InputPin item in _inputPins)
|
||||||
|
item.Name = $"Color #{++index}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnStorageModified(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
UpdateGradient();
|
||||||
|
ComputeInputPins();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGradientCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
// if the user reorders the gradient, let it slide and do nothing.
|
||||||
|
// of course, the user might want to change the input pins since they will no longer line up.
|
||||||
|
if (e.Action == NotifyCollectionChangedAction.Move)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// DisconnectAllInputPins();
|
||||||
|
ComputeInputPins();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@ public class RampSKColorNode : Node<ColorGradient, RampSKColorNodeCustomViewMode
|
|||||||
|
|
||||||
public override void Evaluate()
|
public override void Evaluate()
|
||||||
{
|
{
|
||||||
Output.Value = Storage?.GetColor(Input.Value) ?? SKColor.Empty;
|
Output.Value = Storage?.GetColor(Input.Value % 1.0) ?? SKColor.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:screens="clr-namespace:Artemis.VisualScripting.Nodes.Color.Screens"
|
||||||
|
xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.Controls.GradientPicker;assembly=Artemis.UI.Shared"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Artemis.VisualScripting.Nodes.Color.Screens.ColorGradientNodeCustomView"
|
||||||
|
x:DataType="screens:ColorGradientNodeCustomViewModel">
|
||||||
|
<gradientPicker:GradientPickerButton Classes="condensed"
|
||||||
|
ColorGradient="{CompiledBinding Gradient}"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
FlyoutOpened="GradientPickerButton_OnFlyoutOpened"
|
||||||
|
FlyoutClosed="GradientPickerButton_OnFlyoutClosed" />
|
||||||
|
</UserControl>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using Artemis.UI.Shared.Controls.GradientPicker;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.VisualScripting.Nodes.Color.Screens;
|
||||||
|
|
||||||
|
public class ColorGradientNodeCustomView : ReactiveUserControl<ColorGradientNodeCustomViewModel>
|
||||||
|
{
|
||||||
|
public ColorGradientNodeCustomView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GradientPickerButton_OnFlyoutOpened(GradientPickerButton sender, EventArgs args)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GradientPickerButton_OnFlyoutClosed(GradientPickerButton sender, EventArgs args)
|
||||||
|
{
|
||||||
|
ViewModel?.StoreGradient();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.UI.Shared.Services.NodeEditor;
|
||||||
|
using Artemis.UI.Shared.Services.NodeEditor.Commands;
|
||||||
|
using Artemis.UI.Shared.VisualScripting;
|
||||||
|
|
||||||
|
namespace Artemis.VisualScripting.Nodes.Color.Screens;
|
||||||
|
|
||||||
|
public class ColorGradientNodeCustomViewModel : CustomNodeViewModel
|
||||||
|
{
|
||||||
|
private readonly ColorGradientNode _node;
|
||||||
|
private readonly INodeEditorService _nodeEditorService;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ColorGradientNodeCustomViewModel(ColorGradientNode node, INodeScript script, INodeEditorService nodeEditorService) : base(node, script)
|
||||||
|
{
|
||||||
|
_node = node;
|
||||||
|
_nodeEditorService = nodeEditorService;
|
||||||
|
|
||||||
|
Gradient = _node.Gradient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ColorGradient Gradient { get; }
|
||||||
|
|
||||||
|
public void StoreGradient()
|
||||||
|
{
|
||||||
|
_nodeEditorService.ExecuteCommand(Script, new UpdateStorage<ColorGradient>(_node, Gradient));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -56,6 +56,9 @@ public class DataModelEventNode : Node<DataModelPathEntity, DataModelEventNodeCu
|
|||||||
// If the path is a regular value, evaluate the current value
|
// If the path is a regular value, evaluate the current value
|
||||||
else if (_oldValuePin != null && _newValuePin != null)
|
else if (_oldValuePin != null && _newValuePin != null)
|
||||||
{
|
{
|
||||||
|
if (_newValuePin.IsNumeric)
|
||||||
|
pathValue = new Numeric(pathValue);
|
||||||
|
|
||||||
if (Equals(_lastValue, pathValue))
|
if (Equals(_lastValue, pathValue))
|
||||||
{
|
{
|
||||||
TimeSinceLastTrigger.Value = (DateTime.Now - _lastTrigger).TotalMilliseconds;
|
TimeSinceLastTrigger.Value = (DateTime.Now - _lastTrigger).TotalMilliseconds;
|
||||||
@@ -119,6 +122,9 @@ public class DataModelEventNode : Node<DataModelPathEntity, DataModelEventNodeCu
|
|||||||
if (propertyType == null)
|
if (propertyType == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (Numeric.IsTypeCompatible(propertyType))
|
||||||
|
propertyType = typeof(Numeric);
|
||||||
|
|
||||||
_oldValuePin = CreateOrAddOutputPin(propertyType, "Old value");
|
_oldValuePin = CreateOrAddOutputPin(propertyType, "Old value");
|
||||||
_newValuePin = CreateOrAddOutputPin(propertyType, "New value");
|
_newValuePin = CreateOrAddOutputPin(propertyType, "New value");
|
||||||
_lastValue = null;
|
_lastValue = null;
|
||||||
|
|||||||
Reference in new issue
Block a user