Data bindings - WIP commit

This commit is contained in:
SpoinkyNL committed 2020-09-06 21:24:35 +02:00
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();
}
}
@@ -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);
}
}
}