mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-12 03:48:56 +00:00
Data bindings - WIP commit
This commit is contained in:
1 parent
7bcac904fc
commit
1dc58fd09a
42 files changed
+902
-356
No files matched your search
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Artemis.Core.Models.Profile.DataBindings.Converters
|
||||
{
|
||||
public class FloatDataBindingConverter : IDataBindingConverter
|
||||
{
|
||||
public BaseLayerProperty BaseLayerProperty { get; set; }
|
||||
public object Sum(object a, object b)
|
||||
{
|
||||
return (float) a + (float) b;
|
||||
}
|
||||
|
||||
public object Interpolate(object a, object b, float progress)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void ApplyValue(object value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public object GetValue()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,15 +17,23 @@ namespace Artemis.Core
|
||||
private readonly List<DataBindingModifier> _modifiers = new List<DataBindingModifier>();
|
||||
private bool _isInitialized;
|
||||
|
||||
internal DataBinding(BaseLayerProperty layerProperty, PropertyInfo targetProperty)
|
||||
private object _currentValue;
|
||||
private object _previousValue;
|
||||
private float _easingProgress;
|
||||
|
||||
internal DataBinding(DataBindingRegistration dataBindingRegistration)
|
||||
{
|
||||
LayerProperty = layerProperty;
|
||||
TargetProperty = targetProperty;
|
||||
LayerProperty = dataBindingRegistration.LayerProperty;
|
||||
TargetProperty = dataBindingRegistration.Property;
|
||||
Registration = dataBindingRegistration;
|
||||
|
||||
Entity = new DataBindingEntity();
|
||||
|
||||
ApplyToEntity();
|
||||
}
|
||||
|
||||
public DataBindingRegistration Registration { get; private set; }
|
||||
|
||||
internal DataBinding(BaseLayerProperty layerProperty, DataBindingEntity entity)
|
||||
{
|
||||
LayerProperty = layerProperty;
|
||||
@@ -87,6 +95,8 @@ namespace Artemis.Core
|
||||
{
|
||||
modifier.DataBinding = this;
|
||||
_modifiers.Add(modifier);
|
||||
|
||||
OnModifiersUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +109,8 @@ namespace Artemis.Core
|
||||
{
|
||||
modifier.DataBinding = null;
|
||||
_modifiers.Remove(modifier);
|
||||
|
||||
OnModifiersUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,14 +144,6 @@ namespace Artemis.Core
|
||||
/// <returns></returns>
|
||||
public object GetValue(object baseValue)
|
||||
{
|
||||
// Validating this is kinda expensive, it'll fail on ChangeType later anyway ^^
|
||||
// var targetType = TargetProperty.PropertyType;
|
||||
// if (!targetType.IsCastableFrom(baseValue.GetType()))
|
||||
// {
|
||||
// throw new ArtemisCoreException($"The provided current value type ({baseValue.GetType().Name}) not match the " +
|
||||
// $"target property type ({targetType.Name})");
|
||||
// }
|
||||
|
||||
if (CompiledTargetAccessor == null)
|
||||
return baseValue;
|
||||
|
||||
@@ -147,11 +151,54 @@ namespace Artemis.Core
|
||||
foreach (var dataBindingModifier in Modifiers)
|
||||
dataBindingValue = dataBindingModifier.Apply(dataBindingValue);
|
||||
|
||||
return Convert.ChangeType(dataBindingValue, TargetProperty.PropertyType);
|
||||
var value = Convert.ChangeType(dataBindingValue, TargetProperty.PropertyType);
|
||||
|
||||
// If no easing is to be applied simple return whatever the current value is
|
||||
if (EasingTime == TimeSpan.Zero)
|
||||
return value;
|
||||
|
||||
// If the value changed, update the current and previous values used for easing
|
||||
if (!Equals(value, _currentValue))
|
||||
{
|
||||
_previousValue = GetInterpolatedValue();
|
||||
_currentValue = value;
|
||||
_easingProgress = 0f;
|
||||
}
|
||||
|
||||
// Apply interpolation between the previous and current value
|
||||
return GetInterpolatedValue();
|
||||
}
|
||||
|
||||
internal void Update(double deltaTime)
|
||||
/// <summary>
|
||||
/// Returns the type of the source property of this data binding
|
||||
/// </summary>
|
||||
public Type GetSourceType()
|
||||
{
|
||||
return SourceDataModel?.GetTypeAtPath(SourcePropertyPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the smoothing progress of the data binding
|
||||
/// </summary>
|
||||
/// <param name="deltaTime"></param>
|
||||
public void Update(double deltaTime)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the data binding to the <see cref="Property"/>
|
||||
/// </summary>
|
||||
public void ApplyToProperty()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private object GetInterpolatedValue()
|
||||
{
|
||||
if (_easingProgress >= 1.0f)
|
||||
return _currentValue;
|
||||
|
||||
return Registration.Converter.Interpolate(_previousValue, _currentValue, _easingProgress);
|
||||
}
|
||||
|
||||
internal void ApplyToEntity()
|
||||
@@ -227,6 +274,20 @@ namespace Artemis.Core
|
||||
var lambda = Expression.Lambda<Func<DataModel, object>>(returnValue, parameter);
|
||||
CompiledTargetAccessor = lambda.Compile();
|
||||
}
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a modifier is added or removed
|
||||
/// </summary>
|
||||
public event EventHandler ModifiersUpdated;
|
||||
|
||||
protected virtual void OnModifiersUpdated()
|
||||
{
|
||||
ModifiersUpdated?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Linq.Expressions;
|
||||
using Artemis.Core.DataModelExpansions;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.Storage.Entities.Profile.DataBindings;
|
||||
using LiteDB.Engine;
|
||||
using Microsoft.VisualBasic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
@@ -82,14 +83,9 @@ namespace Artemis.Core
|
||||
public object ParameterStaticValue { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the compiled function that evaluates this predicate if it of a dynamic <see cref="ParameterType" />
|
||||
/// A compiled expression tree that when given a matching data model returns the value of the modifiers parameter
|
||||
/// </summary>
|
||||
public Func<object, DataModel, object> CompiledDynamicPredicate { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the compiled function that evaluates this predicate if it is of a static <see cref="ParameterType" />
|
||||
/// </summary>
|
||||
public Func<object, object> CompiledStaticPredicate { get; private set; }
|
||||
public Func<DataModel, object> CompiledParameterAccessor { get; set; }
|
||||
|
||||
internal DataBindingModifierEntity Entity { get; set; }
|
||||
|
||||
@@ -100,10 +96,14 @@ namespace Artemis.Core
|
||||
/// <returns>The modified value</returns>
|
||||
public object Apply(object currentValue)
|
||||
{
|
||||
if (CompiledDynamicPredicate != null)
|
||||
return CompiledDynamicPredicate(currentValue, ParameterDataModel);
|
||||
if (CompiledStaticPredicate != null)
|
||||
return CompiledStaticPredicate(currentValue);
|
||||
if (ParameterType == ProfileRightSideType.Dynamic && CompiledParameterAccessor != null)
|
||||
{
|
||||
var value = CompiledParameterAccessor(ParameterDataModel);
|
||||
return ModifierType.Apply(currentValue, value);
|
||||
}
|
||||
|
||||
if (ParameterType == ProfileRightSideType.Static)
|
||||
return ModifierType.Apply(currentValue, ParameterStaticValue);
|
||||
|
||||
return currentValue;
|
||||
}
|
||||
@@ -122,7 +122,7 @@ namespace Artemis.Core
|
||||
return;
|
||||
}
|
||||
|
||||
var targetType = DataBinding.TargetProperty.GetType();
|
||||
var targetType = DataBinding.TargetProperty.PropertyType;
|
||||
if (!modifierType.SupportsType(targetType))
|
||||
{
|
||||
throw new ArtemisCoreException($"Cannot apply modifier type {modifierType.GetType().Name} to this modifier because " +
|
||||
@@ -168,7 +168,7 @@ namespace Artemis.Core
|
||||
ParameterDataModel = null;
|
||||
ParameterPropertyPath = null;
|
||||
|
||||
var targetType = DataBinding.TargetProperty.GetType();
|
||||
var targetType = DataBinding.TargetProperty.PropertyType;
|
||||
|
||||
// If not null ensure the types match and if not, convert it
|
||||
if (staticValue != null && staticValue.GetType() == targetType)
|
||||
@@ -208,7 +208,7 @@ namespace Artemis.Core
|
||||
else if (ParameterType == ProfileRightSideType.Static && Entity.ParameterStaticValue != null)
|
||||
{
|
||||
// Use the target type so JSON.NET has a better idea what to do
|
||||
var targetType = DataBinding.TargetProperty.GetType();
|
||||
var targetType = DataBinding.TargetProperty.PropertyType;
|
||||
object staticValue;
|
||||
|
||||
try
|
||||
@@ -231,52 +231,23 @@ namespace Artemis.Core
|
||||
|
||||
private void CreateExpression()
|
||||
{
|
||||
CompiledDynamicPredicate = null;
|
||||
CompiledStaticPredicate = null;
|
||||
CompiledParameterAccessor = null;
|
||||
|
||||
if (ModifierType == null || DataBinding == null)
|
||||
return;
|
||||
|
||||
if (ParameterType == ProfileRightSideType.Dynamic && ModifierType.SupportsParameter)
|
||||
CreateDynamicExpression();
|
||||
else
|
||||
CreateStaticExpression();
|
||||
{
|
||||
if (ParameterDataModel == null)
|
||||
return;
|
||||
|
||||
// If the right side value is null, the constant type cannot be inferred and must be provided based on the data binding target
|
||||
var parameterAccessor = CreateAccessor(ParameterDataModel, ParameterPropertyPath, "parameter", out var rightSideParameter);
|
||||
var lambda = Expression.Lambda<Func<DataModel, object>>(Expression.Convert(parameterAccessor, typeof(object)), rightSideParameter);
|
||||
CompiledParameterAccessor = lambda.Compile();
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateDynamicExpression()
|
||||
{
|
||||
if (ParameterDataModel == null)
|
||||
return;
|
||||
|
||||
var currentValueParameter = Expression.Parameter(DataBinding.TargetProperty.GetType());
|
||||
|
||||
// If the right side value is null, the constant type cannot be inferred and must be provided based on the data binding target
|
||||
var rightSideAccessor = CreateAccessor(ParameterDataModel, ParameterPropertyPath, "right", out var rightSideParameter);
|
||||
|
||||
// A conversion may be required if the types differ
|
||||
// This can cause issues if the DisplayConditionOperator wasn't accurate in it's supported types but that is not a concern here
|
||||
if (rightSideAccessor.Type != DataBinding.TargetProperty.GetType())
|
||||
rightSideAccessor = Expression.Convert(rightSideAccessor, DataBinding.TargetProperty.GetType());
|
||||
|
||||
var modifierExpression = ModifierType.CreateExpression(currentValueParameter, rightSideAccessor);
|
||||
var lambda = Expression.Lambda<Func<object, DataModel, object>>(modifierExpression, currentValueParameter, rightSideParameter);
|
||||
CompiledDynamicPredicate = lambda.Compile();
|
||||
}
|
||||
|
||||
private void CreateStaticExpression()
|
||||
{
|
||||
var currentValueParameter = Expression.Parameter(DataBinding.TargetProperty.GetType());
|
||||
|
||||
// If the right side value is null, the constant type cannot be inferred and must be provided based on the data binding target
|
||||
var rightSideConstant = ParameterStaticValue != null
|
||||
? Expression.Constant(ParameterStaticValue)
|
||||
: Expression.Constant(null, DataBinding.TargetProperty.GetType());
|
||||
|
||||
var modifierExpression = ModifierType.CreateExpression(currentValueParameter, rightSideConstant);
|
||||
var lambda = Expression.Lambda<Func<object, object>>(modifierExpression, currentValueParameter);
|
||||
CompiledStaticPredicate = lambda.Compile();
|
||||
}
|
||||
|
||||
|
||||
private Expression CreateAccessor(DataModel dataModel, string path, string parameterName, out ParameterExpression parameter)
|
||||
{
|
||||
var listType = dataModel.GetListTypeInPath(path);
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
public class DataBindingRegistration
|
||||
{
|
||||
internal DataBindingRegistration(BaseLayerProperty layerProperty, PropertyInfo property, IDataBindingConverter converter)
|
||||
{
|
||||
LayerProperty = layerProperty ?? throw new ArgumentNullException(nameof(layerProperty));
|
||||
Property = property ?? throw new ArgumentNullException(nameof(property));
|
||||
Converter = converter ?? throw new ArgumentNullException(nameof(converter));
|
||||
}
|
||||
|
||||
public BaseLayerProperty LayerProperty { get; set; }
|
||||
public PropertyInfo Property { get; set; }
|
||||
public IDataBindingConverter Converter { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// A data binding converter that acts as the bridge between a <see cref="DataBinding" /> and a
|
||||
/// <see cref="LayerProperty{T}" />
|
||||
/// </summary>
|
||||
public interface IDataBindingConverter
|
||||
{
|
||||
public BaseLayerProperty BaseLayerProperty { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the sum of <paramref name="a" /> and <paramref name="b" />
|
||||
/// </summary>
|
||||
object Sum(object a, object 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>
|
||||
object Interpolate(object a, object b, float progress);
|
||||
|
||||
/// <summary>
|
||||
/// Applies the <paramref name="value" /> to the layer property
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
void ApplyValue(object value);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current base value of the data binding
|
||||
/// </summary>
|
||||
object GetValue();
|
||||
}
|
||||
}
|
||||
+12
-6
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using Artemis.Core.Services;
|
||||
|
||||
namespace Artemis.Core
|
||||
@@ -51,12 +50,19 @@ namespace Artemis.Core
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a binary expression comparing two types
|
||||
/// Called whenever the modifier must apply to a specific value, must be a value of a type contained in
|
||||
/// <see cref="CompatibleTypes" />
|
||||
/// </summary>
|
||||
/// <param name="currentValue">The current value of the data binding</param>
|
||||
/// <param name="modifierArgument">An argument passed to the modifier, either static of dynamic</param>
|
||||
/// <returns></returns>
|
||||
public abstract Expression<object> CreateExpression(ParameterExpression currentValue, Expression modifierArgument);
|
||||
/// <param name="currentValue">
|
||||
/// The current value before modification, is always of a type contained in
|
||||
/// <see cref="CompatibleTypes" />
|
||||
/// </param>
|
||||
/// <param name="parameterValue">
|
||||
/// The parameter to use for the modification, is always of a type contained in
|
||||
/// <see cref="CompatibleTypes" />
|
||||
/// </param>
|
||||
/// <returns>The modified value, must be a value of a type contained in <see cref="CompatibleTypes" /></returns>
|
||||
public abstract object Apply(object currentValue, object parameterValue);
|
||||
|
||||
internal void Register(PluginInfo pluginInfo, IDataBindingService dataBindingService)
|
||||
{
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class DivideModifierType : DataBindingModifierType
|
||||
{
|
||||
public override IReadOnlyCollection<Type> CompatibleTypes => Constants.NumberTypes;
|
||||
|
||||
public override string Description => "Divide by";
|
||||
public override string Icon => "Divide";
|
||||
|
||||
public override object Apply(object currentValue, object parameterValue)
|
||||
{
|
||||
var parameter = Convert.ToSingle(parameterValue);
|
||||
// Ye ye none of that
|
||||
if (parameter == 0)
|
||||
return 0;
|
||||
|
||||
return Convert.ToSingle(currentValue) / parameter;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class MultiplicationModifierType : DataBindingModifierType
|
||||
{
|
||||
public override IReadOnlyCollection<Type> CompatibleTypes => Constants.NumberTypes;
|
||||
|
||||
public override string Description => "Multiply by";
|
||||
public override string Icon => "Close";
|
||||
|
||||
public override object Apply(object currentValue, object parameterValue)
|
||||
{
|
||||
return Convert.ToSingle(currentValue) * Convert.ToSingle(parameterValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user