1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Data bindings - Rewrote data binding system to fully leverage nodes

This commit is contained in:
Robert 2021-08-22 18:17:05 +02:00
parent 7933bf7ee9
commit 0bb93f2ebd
63 changed files with 654 additions and 1533 deletions

View File

@ -84,4 +84,8 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Folder Include="DefaultTypes\DataBindings\" />
</ItemGroup>
</Project>

View File

@ -1,47 +0,0 @@
using System;
namespace Artemis.Core
{
/// <inheritdoc />
public class FloatDataBindingConverter : FloatDataBindingConverter<float>
{
}
/// <inheritdoc />
/// <typeparam name="T">The type of layer property this converter is applied to</typeparam>
public class FloatDataBindingConverter<T> : DataBindingConverter<T, float>
{
/// <summary>
/// Creates a new instance of the <see cref="FloatDataBindingConverter{T}" /> class
/// </summary>
public FloatDataBindingConverter()
{
SupportsSum = true;
SupportsInterpolate = true;
}
/// <inheritdoc />
public override float Sum(float a, float b)
{
return a + b;
}
/// <inheritdoc />
public override float Interpolate(float a, float b, double progress)
{
float diff = b - a;
return (float) (a + diff * progress);
}
/// <inheritdoc />
public override void ApplyValue(float value)
{
if (DataBinding!.LayerProperty.PropertyDescription.MaxInputValue is float max)
value = Math.Min(value, max);
if (DataBinding!.LayerProperty.PropertyDescription.MinInputValue is float min)
value = Math.Max(value, min);
base.ApplyValue(value);
}
}
}

View File

@ -1,33 +0,0 @@
using System;
namespace Artemis.Core
{
/// <summary>
/// Represents a generic data binding converter that acts as the bridge between a
/// <see cref="DataBinding{TLayerProperty, TProperty}" /> and a <see cref="LayerProperty{T}" /> and does not support
/// sum or interpolation
/// </summary>
public class GeneralDataBindingConverter<T> : DataBindingConverter<T, T>
{
/// <summary>
/// Creates a new instance of the <see cref="GeneralDataBindingConverter{T}" /> class
/// </summary>
public GeneralDataBindingConverter()
{
SupportsSum = false;
SupportsInterpolate = false;
}
/// <inheritdoc />
public override T Sum(T a, T b)
{
throw new NotSupportedException();
}
/// <inheritdoc />
public override T Interpolate(T a, T b, double progress)
{
throw new NotSupportedException();
}
}
}

View File

@ -1,41 +0,0 @@
using System;
namespace Artemis.Core
{
/// <inheritdoc />
public class IntDataBindingConverter : IntDataBindingConverter<int>
{
}
/// <inheritdoc />
public class IntDataBindingConverter<T> : DataBindingConverter<T, int>
{
/// <summary>
/// Creates a new instance of the <see cref="IntDataBindingConverter{T}" /> class
/// </summary>
public IntDataBindingConverter()
{
SupportsSum = true;
SupportsInterpolate = true;
}
/// <summary>
/// Gets or sets the <see cref="MidpointRounding" /> mode used for rounding during interpolation. Defaults to
/// <see cref="MidpointRounding.AwayFromZero" />
/// </summary>
public MidpointRounding InterpolationRoundingMode { get; set; } = MidpointRounding.AwayFromZero;
/// <inheritdoc />
public override int Sum(int a, int b)
{
return a + b;
}
/// <inheritdoc />
public override int Interpolate(int a, int b, double progress)
{
int diff = b - a;
return (int) Math.Round(a + diff * progress, InterpolationRoundingMode);
}
}
}

View File

@ -1,29 +0,0 @@
using SkiaSharp;
namespace Artemis.Core
{
/// <inheritdoc />
public class SKColorDataBindingConverter : DataBindingConverter<SKColor, SKColor>
{
/// <summary>
/// Creates a new instance of the <see cref="SKColorDataBindingConverter" /> class
/// </summary>
public SKColorDataBindingConverter()
{
SupportsInterpolate = true;
SupportsSum = true;
}
/// <inheritdoc />
public override SKColor Sum(SKColor a, SKColor b)
{
return a.Sum(b);
}
/// <inheritdoc />
public override SKColor Interpolate(SKColor a, SKColor b, double progress)
{
return a.Interpolate(b, (float) progress);
}
}
}

View File

@ -4,9 +4,14 @@
public class BoolLayerProperty : LayerProperty<bool>
{
internal BoolLayerProperty()
{
}
/// <inheritdoc />
protected override void OnInitialize()
{
KeyframesSupported = false;
RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new GeneralDataBindingConverter<bool>(), "Value");
DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value");
}
/// <summary>

View File

@ -12,7 +12,6 @@ namespace Artemis.Core
internal ColorGradientLayerProperty()
{
KeyframesSupported = false;
DataBindingsSupported = true;
DefaultValue = new ColorGradient();
CurrentValueSet += OnCurrentValueSet;
@ -20,7 +19,7 @@ namespace Artemis.Core
private void CreateDataBindingRegistrations()
{
ClearDataBindingProperties();
DataBinding.ClearDataBindingProperties();
if (CurrentValue == null)
return;
@ -33,7 +32,7 @@ namespace Artemis.Core
CurrentValue[stopIndex].Color = value;
}
RegisterDataBindingProperty(() => CurrentValue[stopIndex].Color, Setter, new ColorStopDataBindingConverter(), $"Color #{stopIndex + 1}");
DataBinding.RegisterDataBindingProperty(() => CurrentValue[stopIndex].Color, Setter, $"Color #{stopIndex + 1}");
}
}
@ -71,7 +70,7 @@ namespace Artemis.Core
private void SubscribedGradientOnPropertyChanged(object? sender, NotifyCollectionChangedEventArgs args)
{
if (CurrentValue.Count != GetAllDataBindingRegistrations().Count)
if (CurrentValue.Count != DataBinding.Properties.Count)
CreateDataBindingRegistrations();
}
@ -89,25 +88,4 @@ namespace Artemis.Core
#endregion
}
internal class ColorStopDataBindingConverter : DataBindingConverter<ColorGradient, SKColor>
{
public ColorStopDataBindingConverter()
{
SupportsInterpolate = true;
SupportsSum = true;
}
/// <inheritdoc />
public override SKColor Sum(SKColor a, SKColor b)
{
return a.Sum(b);
}
/// <inheritdoc />
public override SKColor Interpolate(SKColor a, SKColor b, double progress)
{
return a.Interpolate(b, (float) progress);
}
}
}

View File

@ -8,7 +8,6 @@ namespace Artemis.Core
internal EnumLayerProperty()
{
KeyframesSupported = false;
DataBindingsSupported = false;
}
/// <summary>

View File

@ -5,7 +5,12 @@
{
internal FloatLayerProperty()
{
RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new FloatDataBindingConverter(), "Value");
}
/// <inheritdoc />
protected override void OnInitialize()
{
DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value");
}
/// <summary>

View File

@ -5,13 +5,17 @@
{
internal FloatRangeLayerProperty()
{
RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue.Start = value, new FloatDataBindingConverter<FloatRange>(), "Start");
RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, new FloatDataBindingConverter<FloatRange>(), "End");
CurrentValueSet += OnCurrentValueSet;
DefaultValue = new FloatRange();
}
/// <inheritdoc />
protected override void OnInitialize()
{
DataBinding.RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue.Start = value, "Start");
DataBinding.RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, "End");
}
/// <inheritdoc />
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
{

View File

@ -7,7 +7,12 @@ namespace Artemis.Core
{
internal IntLayerProperty()
{
RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new IntDataBindingConverter(), "Value");
}
/// <inheritdoc />
protected override void OnInitialize()
{
DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value");
}
/// <summary>

View File

@ -5,13 +5,17 @@
{
internal IntRangeLayerProperty()
{
RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue.Start = value, new IntDataBindingConverter<IntRange>(), "Start");
RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, new IntDataBindingConverter<IntRange>(), "End");
CurrentValueSet += OnCurrentValueSet;
DefaultValue = new IntRange();
}
/// <inheritdoc />
protected override void OnInitialize()
{
DataBinding.RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue.Start = value, "Start");
DataBinding.RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, "End");
}
/// <inheritdoc />
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
{

View File

@ -8,7 +8,6 @@
internal LayerBrushReferenceLayerProperty()
{
KeyframesSupported = false;
DataBindingsSupported = false;
}
/// <summary>

View File

@ -7,7 +7,12 @@ namespace Artemis.Core
{
internal SKColorLayerProperty()
{
RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new SKColorDataBindingConverter(), "Value");
}
/// <inheritdoc />
protected override void OnInitialize()
{
DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value");
}
/// <summary>

View File

@ -7,8 +7,13 @@ namespace Artemis.Core
{
internal SKPointLayerProperty()
{
RegisterDataBindingProperty(() => CurrentValue.X, value => CurrentValue = new SKPoint(value, CurrentValue.Y), new FloatDataBindingConverter<SKPoint>(), "X");
RegisterDataBindingProperty(() => CurrentValue.Y, value => CurrentValue = new SKPoint(CurrentValue.X, value), new FloatDataBindingConverter<SKPoint>(), "Y");
}
/// <inheritdoc />
protected override void OnInitialize()
{
DataBinding.RegisterDataBindingProperty(() => CurrentValue.X, value => CurrentValue = new SKPoint(value, CurrentValue.Y), "X");
DataBinding.RegisterDataBindingProperty(() => CurrentValue.Y, value => CurrentValue = new SKPoint(CurrentValue.X, value), "Y");
}
/// <summary>

View File

@ -7,8 +7,13 @@ namespace Artemis.Core
{
internal SKSizeLayerProperty()
{
RegisterDataBindingProperty(() => CurrentValue.Width, (value) => CurrentValue = new SKSize(value, CurrentValue.Height), new FloatDataBindingConverter<SKSize>(), "Width");
RegisterDataBindingProperty(() => CurrentValue.Height, (value) => CurrentValue = new SKSize(CurrentValue.Width, value), new FloatDataBindingConverter<SKSize>(), "Height");
}
/// <inheritdoc />
protected override void OnInitialize()
{
DataBinding.RegisterDataBindingProperty(() => CurrentValue.Width, (value) => CurrentValue = new SKSize(value, CurrentValue.Height), "Width");
DataBinding.RegisterDataBindingProperty(() => CurrentValue.Height, (value) => CurrentValue = new SKSize(CurrentValue.Width, value), "Height");
}
/// <summary>

View File

@ -0,0 +1,20 @@
using System;
namespace Artemis.Core
{
/// <summary>
/// Provides data for data binding events.
/// </summary>
public class DataBindingEventArgs : EventArgs
{
internal DataBindingEventArgs(IDataBinding dataBinding)
{
DataBinding = dataBinding;
}
/// <summary>
/// Gets the data binding this event is related to
/// </summary>
public IDataBinding DataBinding { get; }
}
}

View File

@ -1,25 +1,24 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Artemis.Storage.Entities.Profile.DataBindings;
namespace Artemis.Core
{
/// <inheritdoc />
public class DataBinding<TLayerProperty, TProperty> : IDataBinding
public class DataBinding<TLayerProperty> : IDataBinding
{
private TProperty _currentValue = default!;
private readonly List<IDataBindingProperty> _properties = new();
private bool _disposed;
private TimeSpan _easingProgress;
private TProperty _lastAppliedValue = default!;
private TProperty _previousValue = default!;
private bool _reapplyValue;
private bool _isEnabled;
internal DataBinding(DataBindingRegistration<TLayerProperty, TProperty> dataBindingRegistration)
internal DataBinding(LayerProperty<TLayerProperty> layerProperty)
{
LayerProperty = dataBindingRegistration.LayerProperty;
Entity = new DataBindingEntity();
LayerProperty = layerProperty;
ApplyRegistration(dataBindingRegistration);
Script = new NodeScript<TProperty>(GetScriptName(), "The value to put into the data binding", LayerProperty.ProfileElement.Profile);
Entity = new DataBindingEntity();
Script = new DataBindingNodeScript<TLayerProperty>(GetScriptName(), "The value to put into the data binding", this, LayerProperty.ProfileElement.Profile);
Save();
}
@ -27,39 +26,23 @@ namespace Artemis.Core
internal DataBinding(LayerProperty<TLayerProperty> layerProperty, DataBindingEntity entity)
{
LayerProperty = layerProperty;
Entity = entity;
Script = new NodeScript<TProperty>(GetScriptName(), "The value to put into the data binding", LayerProperty.ProfileElement.Profile);
Script = new DataBindingNodeScript<TLayerProperty>(GetScriptName(), "The value to put into the data binding", this, LayerProperty.ProfileElement.Profile);
// Load will add children so be initialized before that
Load();
}
/// <summary>
/// Gets the data binding registration this data binding is based upon
/// </summary>
public DataBindingRegistration<TLayerProperty, TProperty>? Registration { get; private set; }
/// <summary>
/// Gets the layer property this data binding targets
/// </summary>
public LayerProperty<TLayerProperty> LayerProperty { get; }
/// <summary>
/// Gets the converter used to apply this data binding to the <see cref="LayerProperty" />
/// Gets the script used to populate the data binding
/// </summary>
public DataBindingConverter<TLayerProperty, TProperty>? Converter { get; private set; }
public NodeScript<TProperty> Script { get; private set; }
/// <summary>
/// Gets or sets the easing time of the data binding
/// </summary>
public TimeSpan EasingTime { get; set; }
/// <summary>
/// Gets ors ets the easing function of the data binding
/// </summary>
public Easings.Functions EasingFunction { get; set; }
public DataBindingNodeScript<TLayerProperty> Script { get; private set; }
/// <summary>
/// Gets the data binding entity this data binding uses for persistent storage
@ -67,38 +50,52 @@ namespace Artemis.Core
public DataBindingEntity Entity { get; }
/// <summary>
/// Gets the current value of the data binding
/// Updates the pending values of this data binding
/// </summary>
/// <param name="baseValue">The base value of the property the data binding is applied to</param>
/// <returns></returns>
public TProperty GetValue(TProperty baseValue)
public void Update()
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
if (Converter == null)
return baseValue;
if (!IsEnabled)
return;
// TODO: Update the base node
Script.Run();
// If no easing is to be applied simple return whatever the current value is
if (EasingTime == TimeSpan.Zero || !Converter.SupportsInterpolate)
return Script.Result;
// If the value changed, update the current and previous values used for easing
if (!Equals(Script.Result, _currentValue))
ResetEasing(Script.Result);
// Apply interpolation between the previous and current value
return GetInterpolatedValue();
}
/// <summary>
/// Returns the type of the target property of this data binding
/// Registers a data binding property so that is available to the data binding system
/// </summary>
public Type? GetTargetType()
/// <typeparam name="TProperty">The type of the layer property</typeparam>
/// <param name="getter">The function to call to get the value of the property</param>
/// <param name="setter">The action to call to set the value of the property</param>
/// <param name="displayName">The display name of the data binding property</param>
public DataBindingProperty<TProperty> RegisterDataBindingProperty<TProperty>(Func<TProperty> getter, Action<TProperty?> setter, string displayName)
{
return Registration?.Getter.Method.ReturnType;
if (_disposed)
throw new ObjectDisposedException("DataBinding");
if (Properties.Any(d => d.DisplayName == displayName))
throw new ArtemisCoreException($"A databinding property named '{displayName}' is already registered.");
DataBindingProperty<TProperty> property = new(getter, setter, displayName);
_properties.Add(property);
OnDataBindingPropertyRegistered();
return property;
}
/// <summary>
/// Removes all data binding properties so they are no longer available to the data binding system
/// </summary>
public void ClearDataBindingProperties()
{
if (_disposed)
throw new ObjectDisposedException("LayerProperty");
_properties.Clear();
OnDataBindingPropertiesCleared();
}
/// <summary>
@ -113,111 +110,78 @@ namespace Artemis.Core
if (disposing)
{
_disposed = true;
if (Registration != null)
Registration.DataBinding = null;
Script.Dispose();
}
}
private void ResetEasing(TProperty value)
/// <summary>
/// Invokes the <see cref="DataBindingPropertyRegistered" /> event
/// </summary>
protected virtual void OnDataBindingPropertyRegistered()
{
_previousValue = GetInterpolatedValue();
_currentValue = value;
_easingProgress = TimeSpan.Zero;
}
private void ApplyRegistration(DataBindingRegistration<TLayerProperty, TProperty> dataBindingRegistration)
{
if (dataBindingRegistration == null)
throw new ArgumentNullException(nameof(dataBindingRegistration));
dataBindingRegistration.DataBinding = this;
Converter = dataBindingRegistration.Converter;
Registration = dataBindingRegistration;
if (GetTargetType()!.IsValueType)
{
if (_currentValue == null)
_currentValue = default!;
if (_previousValue == null)
_previousValue = default!;
}
Converter?.Initialize(this);
}
private TProperty GetInterpolatedValue()
{
if (_easingProgress == EasingTime || Converter == null || !Converter.SupportsInterpolate)
return _currentValue;
double easingAmount = _easingProgress.TotalSeconds / EasingTime.TotalSeconds;
return Converter.Interpolate(_previousValue, _currentValue, Easings.Interpolate(easingAmount, EasingFunction));
DataBindingPropertyRegistered?.Invoke(this, new DataBindingEventArgs(this));
}
/// <summary>
/// Updates the smoothing progress of the data binding
/// Invokes the <see cref="DataBindingDisabled" /> event
/// </summary>
/// <param name="timeline">The timeline to apply during update</param>
public void Update(Timeline timeline)
protected virtual void OnDataBindingPropertiesCleared()
{
// Don't update data bindings if there is no delta, otherwise this creates an inconsistency between
// data bindings with easing and data bindings without easing (the ones with easing will seemingly not update)
if (timeline.Delta == TimeSpan.Zero || timeline.IsOverridden)
return;
DataBindingPropertiesCleared?.Invoke(this, new DataBindingEventArgs(this));
}
UpdateWithDelta(timeline.Delta);
/// <summary>
/// Invokes the <see cref="DataBindingEnabled" /> event
/// </summary>
protected virtual void OnDataBindingEnabled(DataBindingEventArgs e)
{
DataBindingEnabled?.Invoke(this, e);
}
/// <summary>
/// Invokes the <see cref="DataBindingDisabled" /> event
/// </summary>
protected virtual void OnDataBindingDisabled(DataBindingEventArgs e)
{
DataBindingDisabled?.Invoke(this, e);
}
private string GetScriptName()
{
return LayerProperty.PropertyDescription.Name ?? LayerProperty.Path;
}
/// <inheritdoc />
public void UpdateWithDelta(TimeSpan delta)
public ILayerProperty BaseLayerProperty => LayerProperty;
/// <inheritdoc />
public bool IsEnabled
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
get => _isEnabled;
set
{
_isEnabled = value;
// Data bindings cannot go back in time like brushes
if (delta < TimeSpan.Zero)
delta = TimeSpan.Zero;
_easingProgress = _easingProgress.Add(delta);
if (_easingProgress > EasingTime)
_easingProgress = EasingTime;
// Tell Apply() to apply a new value next call
_reapplyValue = false;
if (_isEnabled)
OnDataBindingEnabled(new DataBindingEventArgs(this));
else
OnDataBindingDisabled(new DataBindingEventArgs(this));
}
}
/// <inheritdoc />
public ReadOnlyCollection<IDataBindingProperty> Properties => _properties.AsReadOnly();
/// <inheritdoc />
public void Apply()
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
if (Converter == null)
if (!IsEnabled)
return;
// If Update() has not been called, reapply the previous value
if (_reapplyValue)
{
Converter.ApplyValue(_lastAppliedValue);
return;
}
TProperty converterValue = Converter.GetValue();
TProperty value = GetValue(converterValue);
Converter.ApplyValue(value);
_lastAppliedValue = value;
_reapplyValue = true;
}
private string GetScriptName()
{
if (LayerProperty.GetAllDataBindingRegistrations().Count == 1)
return LayerProperty.PropertyDescription.Name ?? LayerProperty.Path;
return $"{LayerProperty.PropertyDescription.Name ?? LayerProperty.Path} - {Registration?.DisplayName}";
Script.DataBindingExitNode.ApplyToDataBinding();
}
/// <inheritdoc />
@ -227,6 +191,19 @@ namespace Artemis.Core
GC.SuppressFinalize(this);
}
/// <inheritdoc />
public event EventHandler<DataBindingEventArgs>? DataBindingPropertyRegistered;
/// <inheritdoc />
public event EventHandler<DataBindingEventArgs>? DataBindingPropertiesCleared;
/// <inheritdoc />
public event EventHandler<DataBindingEventArgs>? DataBindingEnabled;
/// <inheritdoc />
public event EventHandler<DataBindingEventArgs>? DataBindingDisabled;
#region Storage
/// <inheritdoc />
@ -234,14 +211,6 @@ namespace Artemis.Core
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
// General
DataBindingRegistration<TLayerProperty, TProperty>? registration = LayerProperty.GetDataBindingRegistration<TProperty>(Entity.Identifier);
if (registration != null)
ApplyRegistration(registration);
EasingTime = Entity.EasingTime;
EasingFunction = (Easings.Functions) Entity.EasingFunction;
}
/// <inheritdoc />
@ -249,8 +218,8 @@ namespace Artemis.Core
{
Script.Dispose();
Script = Entity.NodeScript != null
? new NodeScript<TProperty>(GetScriptName(), "The value to put into the data binding", Entity.NodeScript, LayerProperty.ProfileElement.Profile)
: new NodeScript<TProperty>(GetScriptName(), "The value to put into the data binding", LayerProperty.ProfileElement.Profile);
? new DataBindingNodeScript<TLayerProperty>(GetScriptName(), "The value to put into the data binding", this, Entity.NodeScript, LayerProperty.ProfileElement.Profile)
: new DataBindingNodeScript<TLayerProperty>(GetScriptName(), "The value to put into the data binding", this, LayerProperty.ProfileElement.Profile);
}
/// <inheritdoc />
@ -259,17 +228,7 @@ namespace Artemis.Core
if (_disposed)
throw new ObjectDisposedException("DataBinding");
if (!LayerProperty.Entity.DataBindingEntities.Contains(Entity))
LayerProperty.Entity.DataBindingEntities.Add(Entity);
// Don't save an invalid state
if (Registration != null)
Entity.Identifier = Registration.DisplayName;
Entity.EasingTime = EasingTime;
Entity.EasingFunction = (int) EasingFunction;
Script?.Save();
Script.Save();
Entity.NodeScript = Script?.Entity;
}

View File

@ -1,90 +0,0 @@
using System;
namespace Artemis.Core
{
/// <summary>
/// Represents a data binding converter that acts as the bridge between a
/// <see cref="DataBinding{TLayerProperty, TProperty}" /> and a <see cref="LayerProperty{T}" />
/// </summary>
public abstract class DataBindingConverter<TLayerProperty, TProperty> : IDataBindingConverter
{
/// <summary>
/// Gets the data binding this converter is applied to
/// </summary>
public DataBinding<TLayerProperty, TProperty>? DataBinding { get; private set; }
/// <summary>
/// Gets whether or not this data binding converter supports the <see cref="Sum" /> method
/// </summary>
public bool SupportsSum { get; protected set; }
/// <summary>
/// Gets whether or not this data binding converter supports the <see cref="Interpolate" /> method
/// </summary>
public bool SupportsInterpolate { get; protected set; }
/// <summary>
/// Returns the sum of <paramref name="a" /> and <paramref name="b" />
/// </summary>
public abstract TProperty Sum(TProperty a, TProperty b);
/// <summary>
/// Returns the the interpolated value between <paramref name="a" /> and <paramref name="b" /> on a scale (generally)
/// between <c>0.0</c> and <c>1.0</c> defined by the <paramref name="progress" />
/// <para>Note: The progress may go be negative or go beyond <c>1.0</c> depending on the easing method used</para>
/// </summary>
/// <param name="a">The value to interpolate away from</param>
/// <param name="b">The value to interpolate towards</param>
/// <param name="progress">The progress of the interpolation between 0.0 and 1.0</param>
/// <returns></returns>
public abstract TProperty Interpolate(TProperty a, TProperty b, double progress);
/// <summary>
/// Applies the <paramref name="value" /> to the layer property
/// </summary>
/// <param name="value"></param>
public virtual void ApplyValue(TProperty value)
{
if (DataBinding?.Registration == null)
throw new ArtemisCoreException("Data binding converter is not yet initialized");
DataBinding.Registration.Setter(value);
}
/// <summary>
/// Returns the current base value of the data binding
/// </summary>
public virtual TProperty GetValue()
{
if (DataBinding?.Registration == null)
throw new ArtemisCoreException("Data binding converter is not yet initialized");
return DataBinding.Registration.Getter();
}
/// <summary>
/// Converts the provided object to a type of <typeparamref name="TProperty" />
/// </summary>
public virtual TProperty ConvertFromObject(object? source)
{
return (TProperty) Convert.ChangeType(source, typeof(TProperty))!;
}
/// <summary>
/// Called when the data binding converter has been initialized and the <see cref="DataBinding" /> is available
/// </summary>
protected virtual void OnInitialized()
{
}
internal void Initialize(DataBinding<TLayerProperty, TProperty> dataBinding)
{
if (dataBinding.Registration == null)
throw new ArtemisCoreException("Cannot initialize a data binding converter for a data binding without a registration");
DataBinding = dataBinding;
OnInitialized();
}
/// <inheritdoc />
public Type SupportedType => typeof(TProperty);
}
}

View File

@ -0,0 +1,46 @@
using System;
namespace Artemis.Core
{
/// <inheritdoc />
public class DataBindingProperty<TProperty> : IDataBindingProperty
{
internal DataBindingProperty(Func<TProperty> getter, Action<TProperty?> setter, string displayName)
{
Getter = getter ?? throw new ArgumentNullException(nameof(getter));
Setter = setter ?? throw new ArgumentNullException(nameof(setter));
DisplayName = displayName ?? throw new ArgumentNullException(nameof(displayName));
}
/// <summary>
/// Gets the function to call to get the value of the property
/// </summary>
public Func<TProperty> Getter { get; }
/// <summary>
/// Gets the action to call to set the value of the property
/// </summary>
public Action<TProperty?> Setter { get; }
/// <inheritdoc />
public string DisplayName { get; }
/// <inheritdoc />
public Type ValueType => typeof(TProperty);
/// <inheritdoc />
public object? GetValue()
{
return Getter();
}
/// <inheritdoc />
public void SetValue(object? value)
{
if (value is TProperty match)
Setter(match);
else
throw new ArgumentException("Value must match the type of the data binding registration", nameof(value));
}
}
}

View File

@ -1,87 +0,0 @@
using System;
using System.Linq;
using Artemis.Storage.Entities.Profile.DataBindings;
namespace Artemis.Core
{
/// <inheritdoc />
public class DataBindingRegistration<TLayerProperty, TProperty> : IDataBindingRegistration
{
internal DataBindingRegistration(LayerProperty<TLayerProperty> layerProperty, DataBindingConverter<TLayerProperty, TProperty> converter,
Func<TProperty> getter, Action<TProperty> setter, string displayName)
{
LayerProperty = layerProperty ?? throw new ArgumentNullException(nameof(layerProperty));
Converter = converter ?? throw new ArgumentNullException(nameof(converter));
Getter = getter ?? throw new ArgumentNullException(nameof(getter));
Setter = setter ?? throw new ArgumentNullException(nameof(setter));
DisplayName = displayName ?? throw new ArgumentNullException(nameof(displayName));
}
/// <summary>
/// Gets the layer property this registration was made on
/// </summary>
public LayerProperty<TLayerProperty> LayerProperty { get; }
/// <summary>
/// Gets the converter that's used by the data binding
/// </summary>
public DataBindingConverter<TLayerProperty, TProperty> Converter { get; }
/// <summary>
/// Gets the function to call to get the value of the property
/// </summary>
public Func<TProperty> Getter { get; }
/// <summary>
/// Gets the action to call to set the value of the property
/// </summary>
public Action<TProperty> Setter { get; }
/// <inheritdoc />
public string DisplayName { get; }
/// <inheritdoc />
public Type ValueType => typeof(TProperty);
/// <summary>
/// Gets the data binding created using this registration
/// </summary>
public DataBinding<TLayerProperty, TProperty>? DataBinding { get; internal set; }
/// <inheritdoc />
public IDataBinding? GetDataBinding()
{
return DataBinding;
}
/// <inheritdoc />
public IDataBinding? CreateDataBinding()
{
if (DataBinding != null)
return DataBinding;
DataBindingEntity? dataBinding = LayerProperty.Entity.DataBindingEntities.FirstOrDefault(e => e.Identifier == DisplayName);
if (dataBinding == null)
return null;
DataBinding = new DataBinding<TLayerProperty, TProperty>(LayerProperty, dataBinding);
return DataBinding;
}
/// <inheritdoc />
public void ClearDataBinding()
{
if (DataBinding == null)
return;
// The related entity is left behind, just in case the data binding is added back later
LayerProperty.DisableDataBinding(DataBinding);
}
/// <inheritdoc />
public object? GetValue()
{
return Getter();
}
}
}

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.ObjectModel;
using Artemis.Core.Modules;
namespace Artemis.Core
@ -7,22 +8,51 @@ namespace Artemis.Core
/// Represents a data binding that binds a certain <see cref="LayerProperty{T}" /> to a value inside a
/// <see cref="DataModel" />
/// </summary>
public interface IDataBinding : IStorageModel, IUpdateModel, IDisposable
public interface IDataBinding : IStorageModel, IDisposable
{
/// <summary>
/// Updates the smoothing progress of the data binding and recalculates the value next <see cref="Apply" /> call
/// Gets the layer property the data binding is applied to
/// </summary>
/// <param name="delta">The delta to apply during update</param>
void UpdateWithDelta(TimeSpan delta);
ILayerProperty BaseLayerProperty { get; }
/// <summary>
/// Applies the data binding to the layer property
/// Gets a list of sub-properties this data binding applies to
/// </summary>
ReadOnlyCollection<IDataBindingProperty> Properties { get; }
/// <summary>
/// Gets a boolean indicating whether the data binding is enabled or not
/// </summary>
bool IsEnabled { get; set; }
/// <summary>
/// Applies the pending value of the data binding to the property
/// </summary>
void Apply();
/// <summary>
/// If the data binding is enabled, loads the node script for that data binding
/// </summary>
void LoadNodeScript();
/// <summary>
/// Occurs when a data binding property has been added
/// </summary>
public event EventHandler<DataBindingEventArgs>? DataBindingPropertyRegistered;
/// <summary>
/// Occurs when all data binding properties have been removed
/// </summary>
public event EventHandler<DataBindingEventArgs>? DataBindingPropertiesCleared;
/// <summary>
/// Occurs when a data binding has been enabled
/// </summary>
public event EventHandler<DataBindingEventArgs>? DataBindingEnabled;
/// <summary>
/// Occurs when a data binding has been disabled
/// </summary>
public event EventHandler<DataBindingEventArgs>? DataBindingDisabled;
}
}

View File

@ -1,16 +0,0 @@
using System;
namespace Artemis.Core
{
/// <summary>
/// Represents a data binding converter that acts as the bridge between a
/// <see cref="DataBinding{TLayerProperty, TProperty}" /> and a <see cref="LayerProperty{T}" />
/// </summary>
public interface IDataBindingConverter
{
/// <summary>
/// Gets the type this converter supports
/// </summary>
public Type SupportedType { get; }
}
}

View File

@ -5,7 +5,7 @@ namespace Artemis.Core
/// <summary>
/// Represents a data binding registration
/// </summary>
public interface IDataBindingRegistration
public interface IDataBindingProperty
{
/// <summary>
/// Gets or sets the display name of the data binding registration
@ -18,25 +18,15 @@ namespace Artemis.Core
Type ValueType { get; }
/// <summary>
/// Returns the data binding applied using this registration
/// </summary>
public IDataBinding? GetDataBinding();
/// <summary>
/// If found, creates a data binding from storage
/// </summary>
/// <returns></returns>
IDataBinding? CreateDataBinding();
/// <summary>
/// If present, removes the current data binding
/// </summary>
void ClearDataBinding();
/// <summary>
/// Gets the value of the data binding
/// Gets the value of the property this registration points to
/// </summary>
/// <returns>A value matching the type of <see cref="ValueType" /></returns>
object? GetValue();
/// <summary>
/// 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);
}
}

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using Artemis.Storage.Entities.Profile;
namespace Artemis.Core
@ -23,6 +22,21 @@ namespace Artemis.Core
/// </summary>
LayerPropertyGroup LayerPropertyGroup { get; }
/// <summary>
/// Gets the data binding of this property
/// </summary>
IDataBinding BaseDataBinding { get; }
/// <summary>
/// Gets a boolean indicating whether the layer has any data binding properties
/// </summary>
public bool HasDataBinding { get; }
/// <summary>
/// Gets a boolean indicating whether data bindings are supported on this type of property
/// </summary>
public bool DataBindingsSupported { get; }
/// <summary>
/// Gets the unique path of the property on the layer
/// </summary>
@ -47,11 +61,6 @@ namespace Artemis.Core
/// </summary>
void Initialize(RenderProfileElement profileElement, LayerPropertyGroup group, PropertyEntity entity, bool fromStorage, PropertyDescriptionAttribute description, string path);
/// <summary>
/// Returns a list off all data binding registrations
/// </summary>
List<IDataBindingRegistration> GetAllDataBindingRegistrations();
/// <summary>
/// Attempts to load and add the provided keyframe entity to the layer property
/// </summary>
@ -70,6 +79,12 @@ namespace Artemis.Core
/// <param name="timeline">The timeline to apply to the property</param>
void Update(Timeline timeline);
/// <summary>
/// Updates just the data binding instead of the entire layer
/// </summary>
void UpdateDataBinding();
/// <summary>
/// Occurs when the layer property is disposed
/// </summary>
@ -104,25 +119,5 @@ namespace Artemis.Core
/// Occurs when a keyframe was removed from the layer property
/// </summary>
public event EventHandler<LayerPropertyEventArgs>? KeyframeRemoved;
/// <summary>
/// Occurs when a data binding property has been added
/// </summary>
public event EventHandler<LayerPropertyEventArgs>? DataBindingPropertyRegistered;
/// <summary>
/// Occurs when all data binding properties have been removed
/// </summary>
public event EventHandler<LayerPropertyEventArgs>? DataBindingPropertiesCleared;
/// <summary>
/// Occurs when a data binding has been enabled
/// </summary>
public event EventHandler<LayerPropertyEventArgs>? DataBindingEnabled;
/// <summary>
/// Occurs when a data binding has been disabled
/// </summary>
public event EventHandler<LayerPropertyEventArgs>? DataBindingDisabled;
}
}

View File

@ -30,6 +30,7 @@ namespace Artemis.Core
Path = null!;
Entity = null!;
PropertyDescription = null!;
DataBinding = null!;
CurrentValue = default!;
DefaultValue = default!;
@ -57,93 +58,10 @@ namespace Artemis.Core
{
_disposed = true;
foreach (IDataBinding dataBinding in _dataBindings)
dataBinding.Dispose();
DataBinding.Dispose();
Disposed?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Invokes the <see cref="Updated" /> event
/// </summary>
protected virtual void OnUpdated()
{
Updated?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="CurrentValueSet" /> event
/// </summary>
protected virtual void OnCurrentValueSet()
{
CurrentValueSet?.Invoke(this, new LayerPropertyEventArgs(this));
LayerPropertyGroup.OnLayerPropertyOnCurrentValueSet(new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="VisibilityChanged" /> event
/// </summary>
protected virtual void OnVisibilityChanged()
{
VisibilityChanged?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="KeyframesToggled" /> event
/// </summary>
protected virtual void OnKeyframesToggled()
{
KeyframesToggled?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="KeyframeAdded" /> event
/// </summary>
protected virtual void OnKeyframeAdded()
{
KeyframeAdded?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="KeyframeRemoved" /> event
/// </summary>
protected virtual void OnKeyframeRemoved()
{
KeyframeRemoved?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="DataBindingPropertyRegistered" /> event
/// </summary>
protected virtual void OnDataBindingPropertyRegistered()
{
DataBindingPropertyRegistered?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="DataBindingDisabled" /> event
/// </summary>
protected virtual void OnDataBindingPropertiesCleared()
{
DataBindingPropertiesCleared?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="DataBindingEnabled" /> event
/// </summary>
protected virtual void OnDataBindingEnabled(LayerPropertyEventArgs e)
{
DataBindingEnabled?.Invoke(this, e);
}
/// <summary>
/// Invokes the <see cref="DataBindingDisabled" /> event
/// </summary>
protected virtual void OnDataBindingDisabled(LayerPropertyEventArgs e)
{
DataBindingDisabled?.Invoke(this, e);
}
/// <inheritdoc />
public PropertyDescriptionAttribute PropertyDescription { get; internal set; }
@ -162,7 +80,16 @@ namespace Artemis.Core
CurrentValue = BaseValue;
UpdateKeyframes(timeline);
UpdateDataBindings(timeline);
UpdateDataBinding();
// UpdateDataBinding called OnUpdated()
}
/// <inheritdoc />
public void UpdateDataBinding()
{
DataBinding.Update();
DataBinding.Apply();
OnUpdated();
}
@ -174,39 +101,6 @@ namespace Artemis.Core
GC.SuppressFinalize(this);
}
/// <inheritdoc />
public event EventHandler? Disposed;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? Updated;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? CurrentValueSet;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? VisibilityChanged;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? KeyframesToggled;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? KeyframeAdded;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? KeyframeRemoved;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? DataBindingPropertyRegistered;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? DataBindingPropertiesCleared;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? DataBindingEnabled;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? DataBindingDisabled;
#region Hierarchy
private bool _isHidden;
@ -480,137 +374,19 @@ namespace Artemis.Core
#region Data bindings
// ReSharper disable InconsistentNaming
internal readonly List<IDataBindingRegistration> _dataBindingRegistrations = new();
internal readonly List<IDataBinding> _dataBindings = new();
// ReSharper restore InconsistentNaming
/// <summary>
/// Gets whether data bindings are supported on this type of property
/// Gets the data binding of this property
/// </summary>
public bool DataBindingsSupported { get; protected internal set; } = true;
public DataBinding<T> DataBinding { get; private set; }
/// <summary>
/// Gets whether the layer has any active data bindings
/// </summary>
public bool HasDataBinding => GetAllDataBindingRegistrations().Any(r => r.GetDataBinding() != null);
/// <inheritdoc />
public bool DataBindingsSupported => DataBinding.Properties.Any();
/// <summary>
/// Gets a data binding registration by the display name used to register it
/// <para>Note: The expression must exactly match the one used to register the data binding</para>
/// </summary>
public DataBindingRegistration<T, TProperty>? GetDataBindingRegistration<TProperty>(string identifier)
{
if (_disposed)
throw new ObjectDisposedException("LayerProperty");
/// <inheritdoc />
public IDataBinding BaseDataBinding => DataBinding;
IDataBindingRegistration? match = _dataBindingRegistrations.FirstOrDefault(r => r is DataBindingRegistration<T, TProperty> registration &&
registration.DisplayName == identifier);
return (DataBindingRegistration<T, TProperty>?) match;
}
/// <summary>
/// Gets a list containing all data binding registrations of this layer property
/// </summary>
/// <returns>A list containing all data binding registrations of this layer property</returns>
public List<IDataBindingRegistration> GetAllDataBindingRegistrations()
{
return _dataBindingRegistrations;
}
/// <summary>
/// Registers a data binding property so that is available to the data binding system
/// </summary>
/// <typeparam name="TProperty">The type of the layer property</typeparam>
/// <param name="getter">The function to call to get the value of the property</param>
/// <param name="setter">The action to call to set the value of the property</param>
/// <param name="converter">The converter to use while applying the data binding</param>
/// <param name="displayName">The display name of the data binding property</param>
public DataBindingRegistration<T, TProperty> RegisterDataBindingProperty<TProperty>(Func<TProperty> getter, Action<TProperty> setter, DataBindingConverter<T, TProperty> converter,
string displayName)
{
if (_disposed)
throw new ObjectDisposedException("LayerProperty");
if (_dataBindingRegistrations.Any(d => d.DisplayName == displayName))
throw new ArtemisCoreException($"A databinding property named '{displayName}' is already registered.");
DataBindingRegistration<T, TProperty> registration = new(this, converter, getter, setter, displayName);
_dataBindingRegistrations.Add(registration);
// If not yet initialized, load the data binding related to the registration if available
if (_isInitialized)
{
IDataBinding? dataBinding = registration.CreateDataBinding();
if (dataBinding != null)
_dataBindings.Add(dataBinding);
}
OnDataBindingPropertyRegistered();
return registration;
}
/// <summary>
/// Removes all data binding properties so they are no longer available to the data binding system
/// </summary>
public void ClearDataBindingProperties()
{
if (_disposed)
throw new ObjectDisposedException("LayerProperty");
foreach (IDataBindingRegistration dataBindingRegistration in _dataBindingRegistrations)
dataBindingRegistration.ClearDataBinding();
_dataBindingRegistrations.Clear();
OnDataBindingPropertiesCleared();
}
/// <summary>
/// Enables a data binding for the provided <paramref name="dataBindingRegistration" />
/// </summary>
/// <returns>The newly created data binding</returns>
public DataBinding<T, TProperty> EnableDataBinding<TProperty>(DataBindingRegistration<T, TProperty> dataBindingRegistration)
{
if (_disposed)
throw new ObjectDisposedException("LayerProperty");
if (dataBindingRegistration.LayerProperty != this)
throw new ArtemisCoreException("Cannot enable a data binding using a data binding registration of a different layer property");
if (dataBindingRegistration.DataBinding != null)
throw new ArtemisCoreException("Provided data binding registration already has an enabled data binding");
DataBinding<T, TProperty> dataBinding = new(dataBindingRegistration);
_dataBindings.Add(dataBinding);
OnDataBindingEnabled(new LayerPropertyEventArgs(dataBinding.LayerProperty));
return dataBinding;
}
/// <summary>
/// Disables the provided data binding
/// </summary>
/// <param name="dataBinding">The data binding to remove</param>
public void DisableDataBinding<TProperty>(DataBinding<T, TProperty> dataBinding)
{
if (_disposed)
throw new ObjectDisposedException("LayerProperty");
_dataBindings.Remove(dataBinding);
if (dataBinding.Registration != null)
dataBinding.Registration.DataBinding = null;
dataBinding.Dispose();
OnDataBindingDisabled(new LayerPropertyEventArgs(dataBinding.LayerProperty));
}
private void UpdateDataBindings(Timeline timeline)
{
foreach (IDataBinding dataBinding in _dataBindings)
{
dataBinding.Update(timeline);
dataBinding.Apply();
}
}
/// <inheritdoc />
public bool HasDataBinding => DataBinding.IsEnabled;
#endregion
@ -694,6 +470,7 @@ namespace Artemis.Core
Entity = entity ?? throw new ArgumentNullException(nameof(entity));
PropertyDescription = description ?? throw new ArgumentNullException(nameof(description));
IsLoadedFromStorage = fromStorage;
DataBinding = new DataBinding<T>(this);
if (PropertyDescription.DisableKeyframes)
KeyframesSupported = false;
@ -737,13 +514,7 @@ namespace Artemis.Core
// ignored for now
}
_dataBindings.Clear();
foreach (IDataBindingRegistration dataBindingRegistration in _dataBindingRegistrations)
{
IDataBinding? dataBinding = dataBindingRegistration.CreateDataBinding();
if (dataBinding != null)
_dataBindings.Add(dataBinding);
}
DataBinding.Load();
}
/// <summary>
@ -762,9 +533,8 @@ namespace Artemis.Core
Entity.KeyframeEntities.Clear();
Entity.KeyframeEntities.AddRange(Keyframes.Select(k => k.GetKeyframeEntity()));
Entity.DataBindingEntities.Clear();
foreach (IDataBinding dataBinding in _dataBindings)
dataBinding.Save();
DataBinding.Save();
Entity.DataBinding = DataBinding.Entity;
}
/// <summary>
@ -775,5 +545,79 @@ namespace Artemis.Core
}
#endregion
#region Events
/// <inheritdoc />
public event EventHandler? Disposed;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? Updated;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? CurrentValueSet;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? VisibilityChanged;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? KeyframesToggled;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? KeyframeAdded;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? KeyframeRemoved;
/// <summary>
/// Invokes the <see cref="Updated" /> event
/// </summary>
protected virtual void OnUpdated()
{
Updated?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="CurrentValueSet" /> event
/// </summary>
protected virtual void OnCurrentValueSet()
{
CurrentValueSet?.Invoke(this, new LayerPropertyEventArgs(this));
LayerPropertyGroup.OnLayerPropertyOnCurrentValueSet(new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="VisibilityChanged" /> event
/// </summary>
protected virtual void OnVisibilityChanged()
{
VisibilityChanged?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="KeyframesToggled" /> event
/// </summary>
protected virtual void OnKeyframesToggled()
{
KeyframesToggled?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="KeyframeAdded" /> event
/// </summary>
protected virtual void OnKeyframeAdded()
{
KeyframeAdded?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="KeyframeRemoved" /> event
/// </summary>
protected virtual void OnKeyframeRemoved()
{
KeyframeRemoved?.Invoke(this, new LayerPropertyEventArgs(this));
}
#endregion
}
}

View File

@ -113,11 +113,8 @@ namespace Artemis.Core
? new NodeScript<bool>($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active", RenderElementEntity.NodeScript, Profile)
: new NodeScript<bool>($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active", Profile);
foreach (ILayerProperty layerProperty in GetAllLayerProperties())
{
foreach (IDataBindingRegistration dataBindingRegistration in layerProperty.GetAllDataBindingRegistrations())
dataBindingRegistration.GetDataBinding()?.LoadNodeScript();
}
foreach (ILayerProperty layerProperty in GetAllLayerProperties())
layerProperty.BaseDataBinding.LoadNodeScript();
}
internal void OnLayerEffectsUpdated()

View File

@ -0,0 +1,36 @@
using System;
using Artemis.Core.Internal;
using Artemis.Storage.Entities.Profile.Nodes;
namespace Artemis.Core
{
public class DataBindingNodeScript<TLayerProperty> : NodeScript
{
#region Properties & Fields
internal DataBindingExitNode<TLayerProperty> DataBindingExitNode { get; }
/// <inheritdoc />
public override Type ResultType => typeof(object);
#endregion
/// <inheritdoc />
public DataBindingNodeScript(string name, string description, DataBinding<TLayerProperty> dataBinding, object? context = null)
: base(name, description, context)
{
DataBindingExitNode = new DataBindingExitNode<TLayerProperty>(dataBinding);
ExitNode = DataBindingExitNode;
AddNode(ExitNode);
}
/// <inheritdoc />
public DataBindingNodeScript(string name, string description, DataBinding<TLayerProperty> dataBinding, NodeScriptEntity entity, object? context = null)
: base(name, description, entity, context)
{
DataBindingExitNode = new DataBindingExitNode<TLayerProperty>(dataBinding);
ExitNode = DataBindingExitNode;
AddNode(ExitNode);
}
}
}

View File

@ -96,7 +96,7 @@ namespace Artemis.Core
private void Evaluate()
{
Value = ConnectedTo.Count > 0 ? ConnectedTo[0].PinValue : default;
Value = ConnectedTo.Count > 0 ? ConnectedTo[0].PinValue : Type.GetDefault();
}
#endregion

View File

@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Artemis.Core.Internal
{
internal class DataBindingExitNode<TLayerProperty> : Node, IExitNode
{
private readonly Dictionary<IDataBindingProperty, InputPin> _propertyPins = new();
private readonly Dictionary<IDataBindingProperty, object> _propertyValues = new();
public DataBinding<TLayerProperty> DataBinding { get; }
public DataBindingExitNode(DataBinding<TLayerProperty> dataBinding) : base(dataBinding.LayerProperty.PropertyDescription.Name ?? "", "")
{
DataBinding = dataBinding;
DataBinding.DataBindingPropertiesCleared += DataBindingOnDataBindingPropertiesCleared;
DataBinding.DataBindingPropertyRegistered += DataBindingOnDataBindingPropertyRegistered;
CreateInputPins();
}
public override bool IsExitNode => true;
public override void Evaluate()
{
foreach (var (property, inputPin) in _propertyPins)
{
if (inputPin.ConnectedTo.Any())
_propertyValues[property] = inputPin.Value;
else
_propertyValues.Remove(property);
}
}
public void ApplyToDataBinding()
{
foreach (var (property, pendingValue) in _propertyValues)
property.SetValue(pendingValue);
}
private void ClearInputPins()
{
while (Pins.Any())
RemovePin((Pin) Pins.First());
_propertyPins.Clear();
_propertyValues.Clear();
}
private void CreateInputPins()
{
ClearInputPins();
foreach (IDataBindingProperty property in DataBinding.Properties)
_propertyPins.Add(property, CreateInputPin(property.ValueType, property.DisplayName));
}
#region Event handlers
private void DataBindingOnDataBindingPropertyRegistered(object? sender, DataBindingEventArgs e)
{
CreateInputPins();
}
private void DataBindingOnDataBindingPropertiesCleared(object? sender, DataBindingEventArgs e)
{
ClearInputPins();
}
#endregion
}
}

View File

@ -1,14 +1,10 @@
using System;
using Artemis.Storage.Entities.Profile.Nodes;
using Artemis.Storage.Entities.Profile.Nodes;
namespace Artemis.Storage.Entities.Profile.DataBindings
{
public class DataBindingEntity
{
public string Identifier { get; set; }
public TimeSpan EasingTime { get; set; }
public int EasingFunction { get; set; }
public NodeScriptEntity NodeScript { get; set; }
}
}

View File

@ -8,16 +8,15 @@ namespace Artemis.Storage.Entities.Profile
public PropertyEntity()
{
KeyframeEntities = new List<KeyframeEntity>();
DataBindingEntities = new List<DataBindingEntity>();
}
public string FeatureId { get; set; }
public string Path { get; set; }
public DataBindingEntity DataBinding { get; set; }
public string Value { get; set; }
public bool KeyframesEnabled { get; set; }
public List<KeyframeEntity> KeyframeEntities { get; set; }
public List<DataBindingEntity> DataBindingEntities { get; set; }
}
}

View File

@ -44,6 +44,11 @@ namespace Artemis.UI.Shared
/// </summary>
public LayerProperty<T> LayerProperty { get; }
/// <summary>
/// Gets a boolean indicating whether the layer property should be enabled
/// </summary>
public bool IsEnabled => !LayerProperty.HasDataBinding;
/// <summary>
/// Gets the profile editor service
/// </summary>
@ -85,8 +90,8 @@ namespace Artemis.UI.Shared
{
LayerProperty.Updated += LayerPropertyOnUpdated;
LayerProperty.CurrentValueSet += LayerPropertyOnUpdated;
LayerProperty.DataBindingEnabled += LayerPropertyOnDataBindingChange;
LayerProperty.DataBindingDisabled += LayerPropertyOnDataBindingChange;
LayerProperty.DataBinding.DataBindingEnabled += OnDataBindingChange;
LayerProperty.DataBinding.DataBindingDisabled += OnDataBindingChange;
UpdateInputValue();
base.OnInitialActivate();
}
@ -96,8 +101,8 @@ namespace Artemis.UI.Shared
{
LayerProperty.Updated -= LayerPropertyOnUpdated;
LayerProperty.CurrentValueSet -= LayerPropertyOnUpdated;
LayerProperty.DataBindingEnabled -= LayerPropertyOnDataBindingChange;
LayerProperty.DataBindingDisabled -= LayerPropertyOnDataBindingChange;
LayerProperty.DataBinding.DataBindingEnabled -= OnDataBindingChange;
LayerProperty.DataBinding.DataBindingDisabled -= OnDataBindingChange;
base.OnClose();
}
@ -194,8 +199,9 @@ namespace Artemis.UI.Shared
UpdateInputValue();
}
private void LayerPropertyOnDataBindingChange(object? sender, LayerPropertyEventArgs e)
private void OnDataBindingChange(object? sender, DataBindingEventArgs e)
{
NotifyOfPropertyChange(nameof(IsEnabled));
OnDataBindingsChanged();
}

View File

@ -37,9 +37,9 @@ namespace Artemis.UI.Shared.Services
RenderProfileElement? SelectedProfileElement { get; }
/// <summary>
/// Gets the currently selected data binding property
/// Gets the currently selected data binding
/// </summary>
ILayerProperty? SelectedDataBinding { get; }
IDataBinding? SelectedDataBinding { get; }
/// <summary>
/// Gets or sets the current time
@ -89,10 +89,10 @@ namespace Artemis.UI.Shared.Services
void SaveSelectedProfileElement();
/// <summary>
/// Changes the selected data binding property
/// Changes the selected data binding
/// </summary>
/// <param name="layerProperty">The data binding property to select</param>
void ChangeSelectedDataBinding(ILayerProperty? layerProperty);
/// <param name="dataBinding">The data binding to select</param>
void ChangeSelectedDataBinding(IDataBinding? dataBinding);
/// <summary>
/// Updates the profile preview, forcing UI-elements to re-render
@ -114,7 +114,7 @@ namespace Artemis.UI.Shared.Services
/// <summary>
/// Registers a new property input view model used in the profile editor for the generic type defined in
/// <see cref="PropertyInputViewModel{T}" />
/// <para>Note: Registration will remove itself on plugin disable so you don't have to</para>
/// <para>Note: DataBindingProperty will remove itself on plugin disable so you don't have to</para>
/// </summary>
/// <param name="plugin"></param>
/// <returns></returns>
@ -123,7 +123,7 @@ namespace Artemis.UI.Shared.Services
/// <summary>
/// Registers a new property input view model used in the profile editor for the generic type defined in
/// <see cref="PropertyInputViewModel{T}" />
/// <para>Note: Registration will remove itself on plugin disable so you don't have to</para>
/// <para>Note: DataBindingProperty will remove itself on plugin disable so you don't have to</para>
/// </summary>
/// <param name="viewModelType"></param>
/// <param name="plugin"></param>

View File

@ -115,7 +115,7 @@ namespace Artemis.UI.Shared.Services
// Trigger selected data binding change
if (SelectedDataBinding != null)
{
SelectedDataBinding = SelectedProfileElement?.GetAllLayerProperties().FirstOrDefault(p => p.Path == SelectedDataBinding.Path);
SelectedDataBinding = SelectedProfileElement?.GetAllLayerProperties().FirstOrDefault(p => p.Path == SelectedDataBinding.BaseLayerProperty.Path)?.BaseDataBinding;
OnSelectedDataBindingChanged();
}
@ -184,7 +184,7 @@ namespace Artemis.UI.Shared.Services
public ProfileConfiguration? SelectedProfileConfiguration { get; private set; }
public Profile? SelectedProfile => SelectedProfileConfiguration?.Profile;
public RenderProfileElement? SelectedProfileElement { get; private set; }
public ILayerProperty? SelectedDataBinding { get; private set; }
public IDataBinding? SelectedDataBinding { get; private set; }
public TimeSpan CurrentTime
{
@ -303,9 +303,9 @@ namespace Artemis.UI.Shared.Services
}
}
public void ChangeSelectedDataBinding(ILayerProperty? layerProperty)
public void ChangeSelectedDataBinding(IDataBinding? dataBinding)
{
SelectedDataBinding = layerProperty;
SelectedDataBinding = dataBinding;
OnSelectedDataBindingChanged();
}

View File

@ -1,6 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using Artemis.Core;
using Artemis.Core;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
@ -8,14 +6,10 @@ namespace Artemis.UI.DefaultTypes.PropertyInput
{
public class BoolPropertyInputViewModel : PropertyInputViewModel<bool>
{
private List<IDataBindingRegistration> _registrations;
public BoolPropertyInputViewModel(LayerProperty<bool> layerProperty, IProfileEditorService profileEditorService) : base(layerProperty, profileEditorService)
{
_registrations = layerProperty.GetAllDataBindingRegistrations();
}
public bool IsEnabled => _registrations.Any(r => r.GetDataBinding() != null);
protected override void OnDataBindingsChanged()
{

View File

@ -5,7 +5,6 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:dataTemplateSelectors="clr-namespace:Artemis.UI.DataTemplateSelectors"
xmlns:layerBrush="clr-namespace:Artemis.Core.LayerBrushes;assembly=Artemis.Core"
xmlns:propertyInput="clr-namespace:Artemis.UI.DefaultTypes.PropertyInput"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"

View File

@ -13,8 +13,8 @@ namespace Artemis.UI.DefaultTypes.PropertyInput
{
public class BrushPropertyInputViewModel : PropertyInputViewModel<LayerBrushReference>
{
private readonly IPluginManagementService _pluginManagementService;
private readonly IDialogService _dialogService;
private readonly IPluginManagementService _pluginManagementService;
private BindableCollection<LayerBrushDescriptor> _descriptors;
public BrushPropertyInputViewModel(LayerProperty<LayerBrushReference> layerProperty, IProfileEditorService profileEditorService, IPluginManagementService pluginManagementService,
@ -51,13 +51,11 @@ namespace Artemis.UI.DefaultTypes.PropertyInput
{
layer.ChangeLayerBrush(SelectedDescriptor);
if (layer.LayerBrush?.Presets != null && layer.LayerBrush.Presets.Any())
{
Execute.PostToUIThread(async () =>
{
await Task.Delay(400);
await _dialogService.ShowDialogAt<LayerBrushPresetViewModel>("LayerProperties", new Dictionary<string, object> {{"layerBrush", layer.LayerBrush}});
});
}
}
}

View File

@ -8,19 +8,9 @@ namespace Artemis.UI.DefaultTypes.PropertyInput
{
public class FloatPropertyInputViewModel : PropertyInputViewModel<float>
{
private readonly DataBindingRegistration<float, float> _registration;
public FloatPropertyInputViewModel(LayerProperty<float> layerProperty, IProfileEditorService profileEditorService, IModelValidator<FloatPropertyInputViewModel> validator)
: base(layerProperty, profileEditorService, validator)
{
_registration = layerProperty.GetDataBindingRegistration<float>("Value");
}
public bool IsEnabled => _registration.DataBinding == null;
protected override void OnDataBindingsChanged()
{
NotifyOfPropertyChange(nameof(IsEnabled));
}
}

View File

@ -27,7 +27,7 @@
Min="{Binding Start}"
DragStarted="{s:Action InputDragStarted}"
DragEnded="{s:Action InputDragEnded}"
IsEnabled="{Binding IsEndEnabled}" />
IsEnabled="{Binding IsEnabled}" />
<TextBlock Width="25" Text="{Binding LayerProperty.PropertyDescription.InputAffix}" VerticalAlignment="Center" />
</StackPanel>
</UserControl>

View File

@ -9,15 +9,10 @@ namespace Artemis.UI.DefaultTypes.PropertyInput
{
public class FloatRangePropertyInputViewModel : PropertyInputViewModel<FloatRange>
{
private readonly DataBindingRegistration<FloatRange, float> _startRegistration;
private readonly DataBindingRegistration<FloatRange, float> _endRegistration;
public FloatRangePropertyInputViewModel(LayerProperty<FloatRange> layerProperty,
IProfileEditorService profileEditorService,
IModelValidator<FloatRangePropertyInputViewModel> validator) : base(layerProperty, profileEditorService, validator)
{
_startRegistration = layerProperty.GetDataBindingRegistration<float>("Start");
_endRegistration = layerProperty.GetDataBindingRegistration<float>("End");
}
public float Start
@ -48,20 +43,12 @@ namespace Artemis.UI.DefaultTypes.PropertyInput
}
}
public bool IsStartEnabled => _startRegistration.DataBinding == null;
public bool IsEndEnabled => _endRegistration.DataBinding == null;
protected override void OnInputValueChanged()
{
NotifyOfPropertyChange(nameof(Start));
NotifyOfPropertyChange(nameof(End));
}
protected override void OnDataBindingsChanged()
{
NotifyOfPropertyChange(nameof(IsStartEnabled));
NotifyOfPropertyChange(nameof(IsEndEnabled));
}
}
public class FloatRangePropertyInputViewModelValidator : AbstractValidator<FloatRangePropertyInputViewModel>

View File

@ -8,19 +8,9 @@ namespace Artemis.UI.DefaultTypes.PropertyInput
{
public class IntPropertyInputViewModel : PropertyInputViewModel<int>
{
private readonly DataBindingRegistration<int, int> _registration;
public IntPropertyInputViewModel(LayerProperty<int> layerProperty, IProfileEditorService profileEditorService, IModelValidator<IntPropertyInputViewModel> validator)
: base(layerProperty, profileEditorService, validator)
{
_registration = layerProperty.GetDataBindingRegistration<int>("Value");
}
public bool IsEnabled => _registration.DataBinding == null;
protected override void OnDataBindingsChanged()
{
NotifyOfPropertyChange(nameof(IsEnabled));
}
}

View File

@ -18,7 +18,7 @@
Min="{Binding LayerProperty.PropertyDescription.MinInputValue}"
DragStarted="{s:Action InputDragStarted}"
DragEnded="{s:Action InputDragEnded}"
IsEnabled="{Binding IsStartEnabled}" />
IsEnabled="{Binding IsEnabled}" />
<TextBlock Margin="5 0" VerticalAlignment="Bottom">-</TextBlock>
<shared:DraggableFloat ToolTip="End"
Value="{Binding End}"
@ -27,7 +27,7 @@
Min="{Binding Start}"
DragStarted="{s:Action InputDragStarted}"
DragEnded="{s:Action InputDragEnded}"
IsEnabled="{Binding IsEndEnabled}" />
IsEnabled="{Binding IsEnabled}" />
<TextBlock Width="25" Text="{Binding LayerProperty.PropertyDescription.InputAffix}" VerticalAlignment="Center" />
</StackPanel>
</UserControl>

View File

@ -9,15 +9,10 @@ namespace Artemis.UI.DefaultTypes.PropertyInput
{
public class IntRangePropertyInputViewModel : PropertyInputViewModel<IntRange>
{
private readonly DataBindingRegistration<IntRange, int> _startRegistration;
private readonly DataBindingRegistration<IntRange, int> _endRegistration;
public IntRangePropertyInputViewModel(LayerProperty<IntRange> layerProperty,
IProfileEditorService profileEditorService,
IModelValidator<IntRangePropertyInputViewModel> validator) : base(layerProperty, profileEditorService, validator)
{
_startRegistration = layerProperty.GetDataBindingRegistration<int>("Start");
_endRegistration = layerProperty.GetDataBindingRegistration<int>("End");
}
public int Start
@ -48,20 +43,12 @@ namespace Artemis.UI.DefaultTypes.PropertyInput
}
}
public bool IsStartEnabled => _startRegistration.DataBinding == null;
public bool IsEndEnabled => _endRegistration.DataBinding == null;
protected override void OnInputValueChanged()
{
NotifyOfPropertyChange(nameof(Start));
NotifyOfPropertyChange(nameof(End));
}
protected override void OnDataBindingsChanged()
{
NotifyOfPropertyChange(nameof(IsStartEnabled));
NotifyOfPropertyChange(nameof(IsEndEnabled));
}
}
public class IntRangePropertyInputViewModelValidator : AbstractValidator<IntRangePropertyInputViewModel>

View File

@ -17,10 +17,10 @@
<artemis:ColorPicker Width="132"
Margin="0 -2 0 3"
Padding="0 -1"
Color="{Binding InputValue, Converter={StaticResource SKColorToColorConverter}}"
Color="{Binding InputValue, Converter={StaticResource SKColorToColorConverter}}"
IsEnabled="{Binding IsEnabled}"
DragStarted="{s:Action InputDragStarted}"
DragEnded="{s:Action InputDragEnded}"/>
DragEnded="{s:Action InputDragEnded}" />
<TextBlock Width="25" Text="{Binding LayerProperty.PropertyDescription.InputAffix}" VerticalAlignment="Center" />
</StackPanel>
</UserControl>

View File

@ -7,18 +7,8 @@ namespace Artemis.UI.DefaultTypes.PropertyInput
{
public class SKColorPropertyInputViewModel : PropertyInputViewModel<SKColor>
{
private readonly DataBindingRegistration<SKColor, SKColor> _registration;
public SKColorPropertyInputViewModel(LayerProperty<SKColor> layerProperty, IProfileEditorService profileEditorService) : base(layerProperty, profileEditorService)
{
_registration = layerProperty.GetDataBindingRegistration<SKColor>("Value");
}
public bool IsEnabled => _registration.DataBinding == null;
protected override void OnDataBindingsChanged()
{
NotifyOfPropertyChange(nameof(IsEnabled));
}
}
}

View File

@ -18,7 +18,7 @@
Min="{Binding LayerProperty.PropertyDescription.MinInputValue}"
DragStarted="{s:Action InputDragStarted}"
DragEnded="{s:Action InputDragEnded}"
IsEnabled="{Binding IsXEnabled}" />
IsEnabled="{Binding IsEnabled}" />
<TextBlock Margin="5 0" VerticalAlignment="Bottom">,</TextBlock>
<shared:DraggableFloat ToolTip="Y-coordinate (vertical)"
Value="{Binding Y}"
@ -27,7 +27,7 @@
Min="{Binding LayerProperty.PropertyDescription.MinInputValue}"
DragStarted="{s:Action InputDragStarted}"
DragEnded="{s:Action InputDragEnded}"
IsEnabled="{Binding IsYEnabled}" />
IsEnabled="{Binding IsEnabled}" />
<TextBlock Width="25" Text="{Binding LayerProperty.PropertyDescription.InputAffix}" VerticalAlignment="Center" />
</StackPanel>
</UserControl>

View File

@ -10,14 +10,9 @@ namespace Artemis.UI.DefaultTypes.PropertyInput
{
public class SKPointPropertyInputViewModel : PropertyInputViewModel<SKPoint>
{
private readonly DataBindingRegistration<SKPoint, float> _xRegistration;
private readonly DataBindingRegistration<SKPoint, float> _yRegistration;
public SKPointPropertyInputViewModel(LayerProperty<SKPoint> layerProperty, IProfileEditorService profileEditorService,
IModelValidator<SKPointPropertyInputViewModel> validator) : base(layerProperty, profileEditorService, validator)
{
_xRegistration = layerProperty.GetDataBindingRegistration<float>("X");
_yRegistration = layerProperty.GetDataBindingRegistration<float>("Y");
}
public float X
@ -32,20 +27,12 @@ namespace Artemis.UI.DefaultTypes.PropertyInput
set => InputValue = new SKPoint(X, value);
}
public bool IsXEnabled => _xRegistration.DataBinding == null;
public bool IsYEnabled => _yRegistration.DataBinding == null;
protected override void OnInputValueChanged()
{
NotifyOfPropertyChange(nameof(X));
NotifyOfPropertyChange(nameof(Y));
}
protected override void OnDataBindingsChanged()
{
NotifyOfPropertyChange(nameof(IsXEnabled));
NotifyOfPropertyChange(nameof(IsYEnabled));
}
}
public class SKPointPropertyInputViewModelValidator : AbstractValidator<SKPointPropertyInputViewModel>

View File

@ -18,7 +18,7 @@
Min="{Binding LayerProperty.PropertyDescription.MinInputValue}"
DragStarted="{s:Action InputDragStarted}"
DragEnded="{s:Action InputDragEnded}"
IsEnabled="{Binding IsHeightEnabled}" />
IsEnabled="{Binding IsEnabled}" />
<TextBlock Margin="5 0" VerticalAlignment="Bottom">,</TextBlock>
<shared:DraggableFloat ToolTip="Width"
Value="{Binding Width}"
@ -27,7 +27,7 @@
Min="{Binding LayerProperty.PropertyDescription.MinInputValue}"
DragStarted="{s:Action InputDragStarted}"
DragEnded="{s:Action InputDragEnded}"
IsEnabled="{Binding IsWidthEnabled}" />
IsEnabled="{Binding IsEnabled}" />
<TextBlock Width="25" Text="{Binding LayerProperty.PropertyDescription.InputAffix}" VerticalAlignment="Center" />
</StackPanel>
</UserControl>

View File

@ -12,14 +12,9 @@ namespace Artemis.UI.DefaultTypes.PropertyInput
{
public class SKSizePropertyInputViewModel : PropertyInputViewModel<SKSize>
{
private readonly DataBindingRegistration<SKSize, float> _heightRegistration;
private readonly DataBindingRegistration<SKSize, float> _widthRegistration;
public SKSizePropertyInputViewModel(LayerProperty<SKSize> layerProperty, IProfileEditorService profileEditorService,
IModelValidator<SKSizePropertyInputViewModel> validator) : base(layerProperty, profileEditorService, validator)
{
_widthRegistration = layerProperty.GetDataBindingRegistration<float>("Width");
_heightRegistration = layerProperty.GetDataBindingRegistration<float>("Height");
}
// Since SKSize is immutable we need to create properties that replace the SKSize entirely
@ -35,20 +30,12 @@ namespace Artemis.UI.DefaultTypes.PropertyInput
set => InputValue = new SKSize(Width, value);
}
public bool IsWidthEnabled => _widthRegistration.DataBinding == null;
public bool IsHeightEnabled => _heightRegistration.DataBinding == null;
protected override void OnInputValueChanged()
{
NotifyOfPropertyChange(nameof(Width));
NotifyOfPropertyChange(nameof(Height));
}
protected override void OnDataBindingsChanged()
{
NotifyOfPropertyChange(nameof(IsWidthEnabled));
NotifyOfPropertyChange(nameof(IsHeightEnabled));
}
}
public class SKSizePropertyInputViewModelValidator : AbstractValidator<SKSizePropertyInputViewModel>

View File

@ -116,11 +116,6 @@ namespace Artemis.UI.Ninject.Factories
NodeScriptWindowViewModel NodeScriptWindowViewModel(NodeScript nodeScript);
}
public interface IDataBindingsVmFactory
{
IDataBindingViewModel DataBindingViewModel(IDataBindingRegistration registration);
}
public interface IPropertyVmFactory
{
ITreePropertyViewModel TreePropertyViewModel(ILayerProperty layerProperty, LayerPropertyViewModel layerPropertyViewModel);

View File

@ -1,26 +0,0 @@
using System;
using System.Reflection;
using Artemis.Core;
using Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings;
using Ninject.Extensions.Factory;
namespace Artemis.UI.Ninject.InstanceProviders
{
public class DataBindingsViewModelInstanceProvider : StandardInstanceProvider
{
protected override Type GetType(MethodInfo methodInfo, object[] arguments)
{
if (methodInfo.ReturnType != typeof(IDataBindingViewModel))
return base.GetType(methodInfo, arguments);
// Find LayerProperty type
Type descriptionPropertyType = arguments[0].GetType();
while (descriptionPropertyType != null && (!descriptionPropertyType.IsGenericType || descriptionPropertyType.GetGenericTypeDefinition() != typeof(DataBindingRegistration<,>)))
descriptionPropertyType = descriptionPropertyType.BaseType;
if (descriptionPropertyType == null)
return base.GetType(methodInfo, arguments);
return typeof(DataBindingViewModel<,>).MakeGenericType(descriptionPropertyType.GetGenericArguments());
}
}
}

View File

@ -56,7 +56,6 @@ namespace Artemis.UI.Ninject
.BindToFactory();
});
Kernel.Bind<IDataBindingsVmFactory>().ToFactory(() => new DataBindingsViewModelInstanceProvider());
Kernel.Bind<IPropertyVmFactory>().ToFactory(() => new LayerPropertyViewModelInstanceProvider());
// Bind profile editor VMs

View File

@ -1,167 +0,0 @@
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DataBindingView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:controls="clr-namespace:Artemis.VisualScripting.Editor.Controls;assembly=Artemis.VisualScripting"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Artemis.UI;component/ResourceDictionaries/DataModelConditions.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.50*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<materialDesign:Card Grid.Column="0"
UniformCornerRadius="0"
materialDesign:ShadowAssist.ShadowDepth="Depth3"
materialDesign:ShadowAssist.ShadowEdges="Right"
Background="{DynamicResource MaterialDesignPaper}"
Panel.ZIndex="2">
<Grid Margin="10 5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<CheckBox Grid.Row="0" Margin="0 10" IsChecked="{Binding IsDataBindingEnabled}">Enable data binding</CheckBox>
<TextBox Grid.Row="1"
Style="{StaticResource MaterialDesignFilledTextBox}"
Margin="0 5"
Text="{Binding EasingTime}"
materialDesign:TextFieldAssist.HasClearButton="True"
materialDesign:TextFieldAssist.SuffixText="ms"
materialDesign:HintAssist.Hint="Easing time"
IsEnabled="{Binding IsDataBindingEnabled}" />
<ComboBox Grid.Row="2"
Style="{StaticResource MaterialDesignFilledComboBox}"
Margin="0 5"
materialDesign:HintAssist.Hint="Easing type"
MinWidth="128"
IsEnabled="{Binding IsEasingTimeEnabled}"
SelectedItem="{Binding SelectedEasingViewModel}"
ItemsSource="{Binding EasingViewModels}">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Polyline Stroke="{DynamicResource MaterialDesignBody}"
StrokeThickness="1"
Points="{Binding EasingPoints}"
Stretch="Uniform"
Width="14"
Height="14"
Margin="0 0 10 0" />
<TextBlock Text="{Binding Description}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<materialDesign:Card Grid.Row="3" Grid.ColumnSpan="2" Margin="0 5 0 0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Margin="10 10 0 0">
Result
</TextBlock>
<TextBlock Grid.Row="0"
Grid.Column="1"
Margin="0 10 10 0"
Visibility="{Binding AlwaysApplyDataBindings.Value, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}}"
ToolTip="Check 'Apply data bindings in editor'"
Cursor="Help">
Other bindings not updating?
</TextBlock>
<Separator Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Style="{StaticResource MaterialDesignLightSeparator}" Margin="0" />
<Grid Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Margin="10 4 10 10">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Margin="0 2" Visibility="{Binding ActiveItem.SupportsTestValue, Converter={x:Static s:BoolToVisibilityConverter.Instance}}">Input</TextBlock>
<ContentControl Grid.Row="0"
Grid.Column="1"
Visibility="{Binding ActiveItem.SupportsTestValue, Converter={x:Static s:BoolToVisibilityConverter.Instance}}"
s:View.Model="{Binding TestInputValue}"
Margin="0 2"
FontFamily="Consolas"
VerticalAlignment="Stretch"
HorizontalAlignment="Right" />
<TextBlock Grid.Row="1" Grid.Column="0" Margin="0 2">Output</TextBlock>
<ContentControl Grid.Row="1"
Grid.Column="1"
s:View.Model="{Binding TestResultValue}"
Margin="0 2"
FontFamily="Consolas"
VerticalAlignment="Stretch"
HorizontalAlignment="Right" />
</Grid>
</Grid>
</materialDesign:Card>
<Grid Grid.Row="4" VerticalAlignment="Bottom" Margin="0 5">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button Grid.Column="0"
Style="{StaticResource MaterialDesignRaisedAccentButton}"
Margin="0 0 5 0"
ToolTip="Copy the entire data binding"
Command="{s:Action CopyDataBinding}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="ContentCopy" />
<TextBlock Text="COPY" Margin="8 0 0 0" />
</StackPanel>
</Button>
<Button Grid.Column="1"
Style="{StaticResource MaterialDesignRaisedAccentButton}"
Margin="5 0 0 0"
ToolTip="Paste the entire data binding"
Command="{s:Action PasteDataBinding}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="ContentPaste" />
<TextBlock Text="PASTE" Margin="8 0 0 0" />
</StackPanel>
</Button>
</Grid>
</Grid>
</materialDesign:Card>
<materialDesign:Card Grid.Column="1" UniformCornerRadius="0" Background="{DynamicResource MaterialDesignToolBarBackground}" Panel.ZIndex="1">
<controls:VisualScriptEditor Script="{Binding Script}" AvailableNodes="{Binding AvailableNodes}"></controls:VisualScriptEditor>
</materialDesign:Card>
</Grid>
</UserControl>

View File

@ -1,15 +0,0 @@
using System.Windows.Controls;
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
{
/// <summary>
/// Interaction logic for DataBindingView.xaml
/// </summary>
public partial class DataBindingView : UserControl
{
public DataBindingView()
{
InitializeComponent();
}
}
}

View File

@ -1,251 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.Storage.Entities.Profile.DataBindings;
using Artemis.UI.Exceptions;
using Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
{
public sealed class DataBindingViewModel<TLayerProperty, TProperty> : Screen, IDataBindingViewModel
{
private readonly ICoreService _coreService;
private readonly IProfileEditorService _profileEditorService;
private int _easingTime;
private bool _isDataBindingEnabled;
private bool _isEasingTimeEnabled;
private DateTime _lastUpdate;
private TimelineEasingViewModel _selectedEasingViewModel;
private bool _updating;
private bool _updatingTestResult;
public DataBindingViewModel(DataBindingRegistration<TLayerProperty, TProperty> registration,
ICoreService coreService,
ISettingsService settingsService,
IProfileEditorService profileEditorService,
IDataModelUIService dataModelUIService,
INodeService nodeService)
{
Registration = registration;
_coreService = coreService;
_profileEditorService = profileEditorService;
DisplayName = Registration.DisplayName.ToUpper();
AlwaysApplyDataBindings = settingsService.GetSetting("ProfileEditor.AlwaysApplyDataBindings", true);
EasingViewModels = new BindableCollection<TimelineEasingViewModel>();
AvailableNodes = nodeService.AvailableNodes;
TestInputValue = dataModelUIService.GetDataModelDisplayViewModel(typeof(TProperty), null, true);
TestResultValue = dataModelUIService.GetDataModelDisplayViewModel(typeof(TProperty), null, true);
}
public DataBindingRegistration<TLayerProperty, TProperty> Registration { get; }
public NodeScript<TProperty> Script => Registration.DataBinding?.Script;
public PluginSetting<bool> AlwaysApplyDataBindings { get; }
public BindableCollection<TimelineEasingViewModel> EasingViewModels { get; }
public IEnumerable<NodeData> AvailableNodes { get; }
public DataModelDisplayViewModel TestInputValue { get; }
public DataModelDisplayViewModel TestResultValue { get; }
public bool IsDataBindingEnabled
{
get => _isDataBindingEnabled;
set
{
SetAndNotify(ref _isDataBindingEnabled, value);
if (_updating)
return;
if (value)
EnableDataBinding();
else
DisableDataBinding();
}
}
public TimelineEasingViewModel SelectedEasingViewModel
{
get => _selectedEasingViewModel;
set
{
if (!SetAndNotify(ref _selectedEasingViewModel, value)) return;
ApplyChanges();
}
}
public int EasingTime
{
get => _easingTime;
set
{
if (!SetAndNotify(ref _easingTime, value)) return;
ApplyChanges();
}
}
public bool IsEasingTimeEnabled
{
get => _isEasingTimeEnabled;
set
{
if (!SetAndNotify(ref _isEasingTimeEnabled, value)) return;
ApplyChanges();
}
}
public void CopyDataBinding()
{
if (Registration.DataBinding != null)
JsonClipboard.SetObject(Registration.DataBinding.Entity);
}
public void PasteDataBinding()
{
if (Registration.DataBinding == null)
Registration.LayerProperty.EnableDataBinding(Registration);
if (Registration.DataBinding == null)
throw new ArtemisUIException("Failed to create a data binding in order to paste");
DataBindingEntity dataBindingEntity = JsonClipboard.GetData<DataBindingEntity>();
if (dataBindingEntity == null)
return;
Registration.DataBinding.EasingTime = dataBindingEntity.EasingTime;
Registration.DataBinding.EasingFunction = (Easings.Functions) dataBindingEntity.EasingFunction;
// TODO - Paste visual script
Update();
_profileEditorService.SaveSelectedProfileElement();
}
protected override void OnInitialActivate()
{
Initialize();
base.OnInitialActivate();
}
private void Initialize()
{
EasingViewModels.AddRange(Enum.GetValues(typeof(Easings.Functions)).Cast<Easings.Functions>().Select(v => new TimelineEasingViewModel(v, false)));
_lastUpdate = DateTime.Now;
_coreService.FrameRendered += OnFrameRendered;
Update();
}
private void Update()
{
if (_updating)
return;
if (Registration.DataBinding == null)
{
IsDataBindingEnabled = false;
IsEasingTimeEnabled = false;
return;
}
_updating = true;
EasingTime = (int) Registration.DataBinding.EasingTime.TotalMilliseconds;
SelectedEasingViewModel = EasingViewModels.First(vm => vm.EasingFunction == Registration.DataBinding.EasingFunction);
IsDataBindingEnabled = true;
IsEasingTimeEnabled = EasingTime > 0;
_updating = false;
}
private void ApplyChanges()
{
if (_updating)
return;
if (Registration.DataBinding != null)
{
Registration.DataBinding.EasingTime = TimeSpan.FromMilliseconds(EasingTime);
Registration.DataBinding.EasingFunction = SelectedEasingViewModel?.EasingFunction ?? Easings.Functions.Linear;
}
_profileEditorService.SaveSelectedProfileElement();
Update();
}
private void UpdateTestResult()
{
if (_updating || _updatingTestResult)
return;
_updatingTestResult = true;
if (Registration.DataBinding == null)
{
TestInputValue.UpdateValue(default);
TestResultValue.UpdateValue(default);
_updatingTestResult = false;
return;
}
// If always update is disabled, do constantly update the data binding as long as the view model is open
// If always update is enabled, this is done for all data bindings in ProfileViewModel
if (!AlwaysApplyDataBindings.Value)
{
Registration.DataBinding.UpdateWithDelta(DateTime.Now - _lastUpdate);
_profileEditorService.UpdateProfilePreview();
}
TProperty currentValue = Registration.Converter.ConvertFromObject(Registration.DataBinding.Script.Result ?? default(TProperty));
TestInputValue.UpdateValue(currentValue);
TestResultValue.UpdateValue(Registration.DataBinding != null ? Registration.DataBinding.GetValue(currentValue) : default);
_updatingTestResult = false;
}
private void EnableDataBinding()
{
if (Registration.DataBinding != null)
return;
Registration.LayerProperty.EnableDataBinding(Registration);
NotifyOfPropertyChange(nameof(Script));
_profileEditorService.SaveSelectedProfileElement();
}
private void DisableDataBinding()
{
if (Registration.DataBinding == null)
return;
Registration.LayerProperty.DisableDataBinding(Registration.DataBinding);
NotifyOfPropertyChange(nameof(Script));
Update();
_profileEditorService.SaveSelectedProfileElement();
}
private void OnFrameRendered(object sender, FrameRenderedEventArgs e)
{
UpdateTestResult();
_lastUpdate = DateTime.Now;
}
/// <inheritdoc />
public void Dispose()
{
_coreService.FrameRendered -= OnFrameRendered;
}
}
public interface IDataBindingViewModel : IScreen, IDisposable
{
}
}

View File

@ -4,20 +4,48 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:controls="clr-namespace:Artemis.VisualScripting.Editor.Controls;assembly=Artemis.VisualScripting"
xmlns:layerProperties="clr-namespace:Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<TabControl ItemsSource="{Binding Items}"
SelectedIndex="{Binding SelectedItemIndex}"
Style="{StaticResource MaterialDesignTabControl}" >
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl s:View.Model="{Binding}" TextElement.Foreground="{DynamicResource MaterialDesignBody}" />
</DataTemplate>
</TabControl.ContentTemplate>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Margin="-12 0" Text="{Binding DisplayName}"></TextBlock>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance {x:Type layerProperties:DataBindingsViewModel}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="48" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}"
VerticalAlignment="Center"
Margin="15 0"
Text="{Binding DataBinding.BaseLayerProperty.PropertyDescription.Name}" />
<CheckBox Grid.Column="1"
VerticalAlignment="Center"
IsChecked="{Binding DataBindingEnabled}">
Enable data binding
</CheckBox>
</Grid>
<controls:VisualScriptPresenter Grid.Row="1" AvailableNodes="{Binding AvailableNodes}" Script="{Binding DataBinding.Script}" />
<Border Grid.Row="1" Background="{StaticResource MaterialDesignToolBarBackground}"
Visibility="{Binding DataBindingEnabled, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Margin="16">
<materialDesign:PackIcon Kind="TransitConnectionVariant" Width="100" Height="100" HorizontalAlignment="Center" />
<TextBlock Style="{StaticResource MaterialDesignHeadline4TextBlock}" TextWrapping="Wrap" HorizontalAlignment="Center" Margin="0 25">
Enable this data binding to use nodes!
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}" TextWrapping="Wrap" HorizontalAlignment="Center" TextAlignment="Center">
When you enable data bindings you can no longer use keyframes or normal values for this property. <LineBreak />
Instead you'll be using our node scripting system to set a value based on the data model or values of other layers/folders.
</TextBlock>
</StackPanel>
</Border>
</Grid>
</UserControl>

View File

@ -1,88 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.UI.Ninject.Factories;
using Artemis.Core.Services;
using Artemis.UI.Shared.Services;
using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
{
public class DataBindingsViewModel : Conductor<IDataBindingViewModel>.Collection.AllActive
public class DataBindingsViewModel : Screen
{
private readonly IDataBindingsVmFactory _dataBindingsVmFactory;
private readonly IProfileEditorService _profileEditorService;
private ILayerProperty _selectedDataBinding;
private int _selectedItemIndex;
private bool _updating;
private IDataBinding _dataBinding;
public DataBindingsViewModel(IProfileEditorService profileEditorService, IDataBindingsVmFactory dataBindingsVmFactory)
public DataBindingsViewModel(IProfileEditorService profileEditorService, INodeService nodeService)
{
_profileEditorService = profileEditorService;
_dataBindingsVmFactory = dataBindingsVmFactory;
AvailableNodes = nodeService.AvailableNodes.ToList();
}
public int SelectedItemIndex
public List<NodeData> AvailableNodes { get; }
public IDataBinding DataBinding
{
get => _selectedItemIndex;
set => SetAndNotify(ref _selectedItemIndex, value);
get => _dataBinding;
set => SetAndNotify(ref _dataBinding, value);
}
private void CreateDataBindingViewModels()
public bool DataBindingEnabled
{
int oldIndex = SelectedItemIndex;
Items.Clear();
ILayerProperty layerProperty = _profileEditorService.SelectedDataBinding;
if (layerProperty == null)
return;
List<IDataBindingRegistration> registrations = layerProperty.GetAllDataBindingRegistrations();
// Create a data binding VM for each data bindable property. These VMs will be responsible for retrieving
// and creating the actual data bindings
Items.AddRange(registrations.Select(registration => _dataBindingsVmFactory.DataBindingViewModel(registration)));
SelectedItemIndex = Items.Count < oldIndex ? 0 : oldIndex;
get => _dataBinding?.IsEnabled ?? false;
set
{
if (_dataBinding != null)
_dataBinding.IsEnabled = value;
}
}
private void ProfileEditorServiceOnSelectedDataBindingChanged(object sender, EventArgs e)
{
CreateDataBindingViewModels();
SubscribeToSelectedDataBinding();
SelectedItemIndex = 0;
}
private void SubscribeToSelectedDataBinding()
{
if (_selectedDataBinding != null)
if (DataBinding != null)
{
_selectedDataBinding.DataBindingPropertyRegistered -= DataBindingRegistrationsChanged;
_selectedDataBinding.DataBindingPropertiesCleared -= DataBindingRegistrationsChanged;
DataBinding.DataBindingEnabled -= DataBindingOnDataBindingToggled;
DataBinding.DataBindingDisabled -= DataBindingOnDataBindingToggled;
}
_selectedDataBinding = _profileEditorService.SelectedDataBinding;
if (_selectedDataBinding != null)
DataBinding = _profileEditorService.SelectedDataBinding;
if (DataBinding != null)
{
_selectedDataBinding.DataBindingPropertyRegistered += DataBindingRegistrationsChanged;
_selectedDataBinding.DataBindingPropertiesCleared += DataBindingRegistrationsChanged;
DataBinding.DataBindingEnabled += DataBindingOnDataBindingToggled;
DataBinding.DataBindingDisabled += DataBindingOnDataBindingToggled;
OnPropertyChanged(nameof(DataBindingEnabled));
}
}
private void DataBindingRegistrationsChanged(object sender, LayerPropertyEventArgs e)
private void DataBindingOnDataBindingToggled(object? sender, DataBindingEventArgs e)
{
if (_updating)
return;
_updating = true;
Execute.PostToUIThread(async () =>
{
await Task.Delay(200);
CreateDataBindingViewModels();
_updating = false;
});
OnPropertyChanged(nameof(DataBindingEnabled));
}
#region Overrides of Screen
@ -90,17 +70,10 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
protected override void OnInitialActivate()
{
_profileEditorService.SelectedDataBindingChanged += ProfileEditorServiceOnSelectedDataBindingChanged;
CreateDataBindingViewModels();
SubscribeToSelectedDataBinding();
base.OnInitialActivate();
}
protected override void OnActivate()
{
SelectedItemIndex = 0;
base.OnActivate();
}
protected override void OnClose()
{
_profileEditorService.SelectedDataBindingChanged -= ProfileEditorServiceOnSelectedDataBindingChanged;

View File

@ -156,7 +156,6 @@
</DockPanel>
</Border>
<!-- Properties tree -->
<materialDesign:DialogHost Identifier="PropertyTreeDialogHost" DialogTheme="Inherit" CloseOnClickAway="True" Grid.Row="1">
<materialDesign:Transitioner x:Name="PropertyTreeTransitioner" SelectedIndex="{Binding PropertyTreeIndex}" DefaultTransitionOrigin="0.9, 0.9" AutoApplyTransitionOrigins="True">

View File

@ -39,10 +39,10 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree
public void ActivateDataBindingViewModel()
{
if (_profileEditorService.SelectedDataBinding == LayerProperty)
if (_profileEditorService.SelectedDataBinding == LayerProperty.DataBinding)
_profileEditorService.ChangeSelectedDataBinding(null);
else
_profileEditorService.ChangeSelectedDataBinding(LayerProperty);
_profileEditorService.ChangeSelectedDataBinding(LayerProperty.DataBinding);
}
public void ResetToDefault()
@ -92,8 +92,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree
{
_profileEditorService.SelectedDataBindingChanged += ProfileEditorServiceOnSelectedDataBindingChanged;
LayerProperty.VisibilityChanged += LayerPropertyOnVisibilityChanged;
LayerProperty.DataBindingEnabled += LayerPropertyOnDataBindingChange;
LayerProperty.DataBindingDisabled += LayerPropertyOnDataBindingChange;
LayerProperty.DataBinding.DataBindingEnabled += LayerPropertyOnDataBindingChange;
LayerProperty.DataBinding.DataBindingDisabled += LayerPropertyOnDataBindingChange;
LayerProperty.KeyframesToggled += LayerPropertyOnKeyframesToggled;
LayerPropertyViewModel.IsVisible = !LayerProperty.IsHidden;
base.OnInitialActivate();
@ -104,8 +104,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree
{
_profileEditorService.SelectedDataBindingChanged -= ProfileEditorServiceOnSelectedDataBindingChanged;
LayerProperty.VisibilityChanged -= LayerPropertyOnVisibilityChanged;
LayerProperty.DataBindingEnabled -= LayerPropertyOnDataBindingChange;
LayerProperty.DataBindingDisabled -= LayerPropertyOnDataBindingChange;
LayerProperty.DataBinding.DataBindingEnabled -= LayerPropertyOnDataBindingChange;
LayerProperty.DataBinding.DataBindingDisabled -= LayerPropertyOnDataBindingChange;
LayerProperty.KeyframesToggled -= LayerPropertyOnKeyframesToggled;
base.OnClose();
}
@ -116,7 +116,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree
private void ProfileEditorServiceOnSelectedDataBindingChanged(object sender, EventArgs e)
{
LayerPropertyViewModel.IsHighlighted = _profileEditorService.SelectedDataBinding == LayerProperty;
LayerPropertyViewModel.IsHighlighted = _profileEditorService.SelectedDataBinding == LayerProperty.DataBinding;
}
private void LayerPropertyOnVisibilityChanged(object sender, LayerPropertyEventArgs e)
@ -124,7 +124,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree
LayerPropertyViewModel.IsVisible = !LayerProperty.IsHidden;
}
private void LayerPropertyOnDataBindingChange(object sender, LayerPropertyEventArgs e)
private void LayerPropertyOnDataBindingChange(object sender, DataBindingEventArgs e)
{
NotifyOfPropertyChange(nameof(HasDataBinding));
}

View File

@ -30,7 +30,6 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization
private bool _canSelectEditTool;
private BindableCollection<ArtemisDevice> _devices;
private BindableCollection<ArtemisLed> _highlightedLeds;
private DateTime _lastUpdate;
private PanZoomViewModel _panZoomViewModel;
private Layer _previousSelectedLayer;
private int _previousTool;
@ -138,7 +137,6 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization
ApplyActiveProfile();
_lastUpdate = DateTime.Now;
_coreService.FrameRendered += OnFrameRendered;
_rgbService.DeviceAdded += RgbServiceOnDevicesModified;
@ -232,7 +230,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization
if (CanSelectEditTool == false && ActiveToolIndex == 1)
ActivateToolByIndex(2);
}
#region Buttons
private void ActivateToolByIndex(int value)
@ -307,19 +305,32 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization
private void OnFrameRendered(object sender, FrameRenderedEventArgs e)
{
TimeSpan delta = DateTime.Now - _lastUpdate;
_lastUpdate = DateTime.Now;
if (SuspendedEditing || !_settingsService.GetSetting("ProfileEditor.AlwaysApplyDataBindings", true).Value || _profileEditorService.SelectedProfile == null)
if (SuspendedEditing || _profileEditorService.Playing || _profileEditorService.SelectedProfile == null)
return;
foreach (IDataBindingRegistration dataBindingRegistration in _profileEditorService.SelectedProfile.GetAllRenderElements()
.SelectMany(f => f.GetAllLayerProperties(), (f, p) => p)
.SelectMany(p => p.GetAllDataBindingRegistrations()))
dataBindingRegistration.GetDataBinding()?.UpdateWithDelta(delta);
// TODO: Only update when there are data bindings
if (!_profileEditorService.Playing)
bool hasEnabledDataBindings = false;
// Always update the currently selected data binding
if (_profileEditorService.SelectedDataBinding != null && _profileEditorService.SelectedDataBinding.IsEnabled)
{
hasEnabledDataBindings = true;
_profileEditorService.SelectedDataBinding.BaseLayerProperty.UpdateDataBinding();
}
// Update all other data bindings if the user enabled this
if (_settingsService.GetSetting("ProfileEditor.AlwaysApplyDataBindings", true).Value)
{
foreach (ILayerProperty layerProperty in _profileEditorService.SelectedProfile.GetAllRenderElements().SelectMany(f => f.GetAllLayerProperties(), (_, p) => p))
{
if (layerProperty.BaseDataBinding != _profileEditorService.SelectedDataBinding && layerProperty.BaseDataBinding.IsEnabled)
{
hasEnabledDataBindings = true;
layerProperty.UpdateDataBinding();
}
}
}
if (hasEnabledDataBindings)
_profileEditorService.UpdateProfilePreview();
}

View File

@ -61,7 +61,7 @@ namespace Artemis.VisualScripting.Nodes.CustomViewModels
if (_node.ProfileElement == null)
return;
LayerProperties.AddRange(_node.ProfileElement.GetAllLayerProperties().Where(l => l.GetAllDataBindingRegistrations().Any()));
LayerProperties.AddRange(_node.ProfileElement.GetAllLayerProperties().Where(l => l.DataBindingsSupported));
_selectedLayerProperty = _node.LayerProperty;
NotifyOfPropertyChange(nameof(SelectedLayerProperty));
}

View File

@ -26,16 +26,16 @@ namespace Artemis.VisualScripting.Nodes
return;
}
List<IDataBindingRegistration> list = LayerProperty.GetAllDataBindingRegistrations();
List<IDataBindingProperty> list = LayerProperty.BaseDataBinding.Properties.ToList();
int index = 0;
foreach (IPin pin in Pins)
{
OutputPin outputPin = (OutputPin) pin;
IDataBindingRegistration dataBindingRegistration = list[index];
IDataBindingProperty dataBindingProperty = list[index];
index++;
// TODO: Is this really non-nullable?
outputPin.Value = dataBindingRegistration.GetValue();
outputPin.Value = dataBindingProperty.GetValue();
}
}
}
@ -108,7 +108,7 @@ namespace Artemis.VisualScripting.Nodes
if (LayerProperty == null)
return;
foreach (IDataBindingRegistration dataBindingRegistration in LayerProperty.GetAllDataBindingRegistrations())
foreach (IDataBindingProperty dataBindingRegistration in LayerProperty.BaseDataBinding.Properties)
CreateOutputPin(dataBindingRegistration.ValueType, dataBindingRegistration.DisplayName);
}