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

Data bindings - Updated to new paths API

Data bindings - Fixed profile editor behavior
This commit is contained in:
SpoinkyNL 2020-10-10 20:24:25 +02:00
parent 69ae42c039
commit 7610aeae4b
9 changed files with 264 additions and 394 deletions

View File

@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using Artemis.Storage.Entities.Profile.DataBindings; using Artemis.Storage.Entities.Profile.DataBindings;
namespace Artemis.Core namespace Artemis.Core
@ -7,8 +6,6 @@ namespace Artemis.Core
/// <inheritdoc /> /// <inheritdoc />
public class DataBinding<TLayerProperty, TProperty> : IDataBinding public class DataBinding<TLayerProperty, TProperty> : IDataBinding
{ {
private readonly List<DataBindingModifier<TLayerProperty, TProperty>> _modifiers = new List<DataBindingModifier<TLayerProperty, TProperty>>();
private TProperty _currentValue; private TProperty _currentValue;
private bool _disposed; private bool _disposed;
private TimeSpan _easingProgress; private TimeSpan _easingProgress;
@ -63,51 +60,9 @@ namespace Artemis.Core
/// Gets ors ets the easing function of the data binding /// Gets ors ets the easing function of the data binding
/// </summary> /// </summary>
public Easings.Functions EasingFunction { get; set; } public Easings.Functions EasingFunction { get; set; }
internal DataBindingEntity Entity { get; } internal DataBindingEntity Entity { get; }
/// <summary>
/// Updates the smoothing progress of the data binding
/// </summary>
/// <param name="deltaTime">The time in seconds that passed since the last update</param>
public void Update(double deltaTime)
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
// Data bindings cannot go back in time like brushes
deltaTime = Math.Max(0, deltaTime);
_easingProgress = _easingProgress.Add(TimeSpan.FromSeconds(deltaTime));
if (_easingProgress > EasingTime)
_easingProgress = EasingTime;
}
/// <inheritdoc />
public void Apply()
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
if (Converter == null)
return;
TProperty converterValue = Converter.GetValue();
TProperty value = GetValue(converterValue);
Converter.ApplyValue(value);
}
/// <inheritdoc />
public void Dispose()
{
_disposed = true;
Registration.DataBinding = null;
DataBindingMode?.Dispose();
}
/// <summary> /// <summary>
/// Gets the current value of the data binding /// Gets the current value of the data binding
/// </summary> /// </summary>
@ -179,6 +134,46 @@ namespace Artemis.Core
return Converter.Interpolate(_previousValue, _currentValue, Easings.Interpolate(easingAmount, EasingFunction)); return Converter.Interpolate(_previousValue, _currentValue, Easings.Interpolate(easingAmount, EasingFunction));
} }
/// <summary>
/// Updates the smoothing progress of the data binding
/// </summary>
/// <param name="deltaTime">The time in seconds that passed since the last update</param>
public void Update(double deltaTime)
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
// Data bindings cannot go back in time like brushes
deltaTime = Math.Max(0, deltaTime);
_easingProgress = _easingProgress.Add(TimeSpan.FromSeconds(deltaTime));
if (_easingProgress > EasingTime)
_easingProgress = EasingTime;
}
/// <inheritdoc />
public void Apply()
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
if (Converter == null)
return;
TProperty converterValue = Converter.GetValue();
TProperty value = GetValue(converterValue);
Converter.ApplyValue(value);
}
/// <inheritdoc />
public void Dispose()
{
_disposed = true;
Registration.DataBinding = null;
DataBindingMode?.Dispose();
}
#region Mode management #region Mode management
/// <summary> /// <summary>

View File

@ -82,7 +82,7 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Converts the provided object to a type of <typeparamref name="TProperty" /> /// Converts the provided object to a type of <typeparamref name="TProperty" />
/// </summary> /// </summary>
public virtual TProperty ConvertFromObject(object source) public virtual TProperty ConvertFromObject(object? source)
{ {
return (TProperty) Convert.ChangeType(source, typeof(TProperty)); return (TProperty) Convert.ChangeType(source, typeof(TProperty));
} }

View File

@ -1,6 +1,4 @@
using System; using System;
using System.Linq.Expressions;
using Artemis.Core.DataModelExpansions;
using Artemis.Storage.Entities.Profile.DataBindings; using Artemis.Storage.Entities.Profile.DataBindings;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -50,14 +48,9 @@ namespace Artemis.Core
public int Order { get; set; } public int Order { get; set; }
/// <summary> /// <summary>
/// Gets the currently used instance of the parameter data model /// Gets the path of the parameter property
/// </summary> /// </summary>
public DataModel ParameterDataModel { get; private set; } public DataModelPath? ParameterPath { get; set; }
/// <summary>
/// Gets the path of the parameter property in the <see cref="ParameterDataModel" />
/// </summary>
public string ParameterPropertyPath { get; private set; }
/// <summary> /// <summary>
/// Gets the parameter static value, only used it <see cref="ParameterType" /> is /// Gets the parameter static value, only used it <see cref="ParameterType" /> is
@ -65,13 +58,144 @@ namespace Artemis.Core
/// </summary> /// </summary>
public object ParameterStaticValue { get; private set; } public object ParameterStaticValue { get; private set; }
/// <summary>
/// A compiled expression tree that when given a matching data model returns the value of the modifiers parameter
/// </summary>
public Func<DataModel, object> CompiledParameterAccessor { get; set; }
internal DataBindingModifierEntity Entity { get; set; } internal DataBindingModifierEntity Entity { get; set; }
/// <summary>
/// Applies the modifier to the provided value
/// </summary>
/// <param name="currentValue">The value to apply the modifier to, should be of the same type as the data binding target</param>
/// <returns>The modified value</returns>
public object Apply(object? currentValue)
{
if (_disposed)
throw new ObjectDisposedException("DataBindingModifier");
if (ModifierType == null)
return currentValue;
if (!ModifierType.SupportsParameter)
return ModifierType.Apply(currentValue, null);
if (ParameterType == ProfileRightSideType.Dynamic && ParameterPath != null && ParameterPath.IsValid)
{
object? value = ParameterPath.GetValue();
return ModifierType.Apply(currentValue, value);
}
if (ParameterType == ProfileRightSideType.Static)
return ModifierType.Apply(currentValue, ParameterStaticValue);
return currentValue;
}
/// <summary>
/// Updates the modifier type of the modifier and re-compiles the expression
/// </summary>
/// <param name="modifierType"></param>
public void UpdateModifierType(DataBindingModifierType modifierType)
{
if (_disposed)
throw new ObjectDisposedException("DataBindingModifier");
// Calling CreateExpression will clear compiled expressions
if (modifierType == null)
{
ModifierType = null;
return;
}
Type targetType = DirectDataBinding.DataBinding.GetTargetType();
if (!modifierType.SupportsType(targetType))
throw new ArtemisCoreException($"Cannot apply modifier type {modifierType.GetType().Name} to this modifier because " +
$"it does not support this data binding's type {targetType.Name}");
ModifierType = modifierType;
}
/// <summary>
/// Updates the parameter of the modifier and makes the modifier dynamic
/// </summary>
/// <param name="path">The path pointing to the parameter</param>
public void UpdateParameterDynamic(DataModelPath? path)
{
if (_disposed)
throw new ObjectDisposedException("DataBindingModifier");
if (path != null && !path.IsValid)
throw new ArtemisCoreException("Cannot update parameter of data binding modifier to an invalid path");
ParameterPath?.Dispose();
ParameterPath = path != null ? new DataModelPath(path) : null;
ParameterType = ProfileRightSideType.Dynamic;
}
/// <summary>
/// Updates the parameter of the modifier, makes the modifier static and re-compiles the expression
/// </summary>
/// <param name="staticValue">The static value to use as a parameter</param>
public void UpdateParameterStatic(object staticValue)
{
if (_disposed)
throw new ObjectDisposedException("DataBindingModifier");
ParameterType = ProfileRightSideType.Static;
ParameterPath?.Dispose();
ParameterPath = null;
Type parameterType = ModifierType?.ParameterType ?? DirectDataBinding.DataBinding.GetTargetType();
// If not null ensure the types match and if not, convert it
if (staticValue != null && staticValue.GetType() == parameterType)
ParameterStaticValue = staticValue;
else if (staticValue != null)
ParameterStaticValue = Convert.ChangeType(staticValue, parameterType);
// If null create a default instance for value types or simply make it null for reference types
else if (parameterType.IsValueType)
ParameterStaticValue = Activator.CreateInstance(parameterType);
else
ParameterStaticValue = null;
}
private void Initialize()
{
DataBindingModifierTypeStore.DataBindingModifierAdded += DataBindingModifierTypeStoreOnDataBindingModifierAdded;
DataBindingModifierTypeStore.DataBindingModifierRemoved += DataBindingModifierTypeStoreOnDataBindingModifierRemoved;
// Modifier type
if (Entity.ModifierTypePluginGuid != null && ModifierType == null)
{
DataBindingModifierType modifierType = DataBindingModifierTypeStore.Get(Entity.ModifierTypePluginGuid.Value, Entity.ModifierType)?.DataBindingModifierType;
if (modifierType != null)
UpdateModifierType(modifierType);
}
// Dynamic parameter
if (ParameterType == ProfileRightSideType.Dynamic && Entity.ParameterPath != null)
{
ParameterPath = new DataModelPath(null, Entity.ParameterPath);
}
// Static parameter
else if (ParameterType == ProfileRightSideType.Static && Entity.ParameterStaticValue != null && ParameterStaticValue == null)
{
// Use the target type so JSON.NET has a better idea what to do
Type parameterType = ModifierType?.ParameterType ?? DirectDataBinding.DataBinding.GetTargetType();
object staticValue;
try
{
staticValue = JsonConvert.DeserializeObject(Entity.ParameterStaticValue, parameterType);
}
// If deserialization fails, use the type's default
catch (JsonSerializationException e)
{
DeserializationLogger.LogModifierDeserializationFailure(GetType().Name, e);
staticValue = Activator.CreateInstance(parameterType);
}
UpdateParameterStatic(staticValue);
}
}
/// <inheritdoc /> /// <inheritdoc />
public void Save() public void Save()
@ -79,6 +203,10 @@ namespace Artemis.Core
if (_disposed) if (_disposed)
throw new ObjectDisposedException("DataBindingModifier"); throw new ObjectDisposedException("DataBindingModifier");
// Don't save an invalid state
if (ParameterPath != null && !ParameterPath.IsValid)
return;
if (!DirectDataBinding.Entity.Modifiers.Contains(Entity)) if (!DirectDataBinding.Entity.Modifiers.Contains(Entity))
DirectDataBinding.Entity.Modifiers.Add(Entity); DirectDataBinding.Entity.Modifiers.Add(Entity);
@ -94,11 +222,8 @@ namespace Artemis.Core
Entity.ParameterType = (int) ParameterType; Entity.ParameterType = (int) ParameterType;
// Parameter // Parameter
if (ParameterDataModel != null) ParameterPath?.Save();
{ Entity.ParameterPath = ParameterPath?.Entity;
Entity.ParameterDataModelGuid = ParameterDataModel.PluginInfo.Guid;
Entity.ParameterPropertyPath = ParameterPropertyPath;
}
Entity.ParameterStaticValue = JsonConvert.SerializeObject(ParameterStaticValue); Entity.ParameterStaticValue = JsonConvert.SerializeObject(ParameterStaticValue);
} }
@ -125,186 +250,8 @@ namespace Artemis.Core
DataBindingModifierTypeStore.DataBindingModifierAdded -= DataBindingModifierTypeStoreOnDataBindingModifierAdded; DataBindingModifierTypeStore.DataBindingModifierAdded -= DataBindingModifierTypeStoreOnDataBindingModifierAdded;
DataBindingModifierTypeStore.DataBindingModifierRemoved -= DataBindingModifierTypeStoreOnDataBindingModifierRemoved; DataBindingModifierTypeStore.DataBindingModifierRemoved -= DataBindingModifierTypeStoreOnDataBindingModifierRemoved;
DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded;
DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved;
}
/// <summary> ParameterPath?.Dispose();
/// Applies the modifier to the provided value
/// </summary>
/// <param name="currentValue">The value to apply the modifier to, should be of the same type as the data binding target</param>
/// <returns>The modified value</returns>
public object Apply(object currentValue)
{
if (_disposed)
throw new ObjectDisposedException("DataBindingModifier");
if (ModifierType == null)
return currentValue;
if (!ModifierType.SupportsParameter)
return ModifierType.Apply(currentValue, null);
if (ParameterType == ProfileRightSideType.Dynamic && CompiledParameterAccessor != null)
{
object value = CompiledParameterAccessor(ParameterDataModel);
return ModifierType.Apply(currentValue, value);
}
if (ParameterType == ProfileRightSideType.Static)
return ModifierType.Apply(currentValue, ParameterStaticValue);
return currentValue;
}
/// <summary>
/// Updates the modifier type of the modifier and re-compiles the expression
/// </summary>
/// <param name="modifierType"></param>
public void UpdateModifierType(DataBindingModifierType modifierType)
{
if (_disposed)
throw new ObjectDisposedException("DataBindingModifier");
// Calling CreateExpression will clear compiled expressions
if (modifierType == null)
{
ModifierType = null;
CreateExpression();
return;
}
Type targetType = DirectDataBinding.DataBinding.GetTargetType();
if (!modifierType.SupportsType(targetType))
{
throw new ArtemisCoreException($"Cannot apply modifier type {modifierType.GetType().Name} to this modifier because " +
$"it does not support this data binding's type {targetType.Name}");
}
ModifierType = modifierType;
CreateExpression();
}
/// <summary>
/// Updates the parameter of the modifier, makes the modifier dynamic and re-compiles the expression
/// </summary>
/// <param name="dataModel">The data model of the parameter</param>
/// <param name="path">The path pointing to the parameter inside the data model</param>
public void UpdateParameter(DataModel? dataModel, string? path)
{
if (_disposed)
throw new ObjectDisposedException("DataBindingModifier");
if (dataModel != null && path == null)
throw new ArtemisCoreException("If a data model is provided, a path is also required");
if (dataModel == null && path != null)
throw new ArtemisCoreException("If path is provided, a data model is also required");
if (dataModel != null)
{
if (!dataModel.ContainsPath(path))
throw new ArtemisCoreException($"Data model of type {dataModel.GetType().Name} does not contain a property at path '{path}'");
}
ParameterType = ProfileRightSideType.Dynamic;
ParameterDataModel = dataModel;
ParameterPropertyPath = path;
CreateExpression();
}
/// <summary>
/// Updates the parameter of the modifier, makes the modifier static and re-compiles the expression
/// </summary>
/// <param name="staticValue">The static value to use as a parameter</param>
public void UpdateParameter(object staticValue)
{
if (_disposed)
throw new ObjectDisposedException("DataBindingModifier");
ParameterType = ProfileRightSideType.Static;
ParameterDataModel = null;
ParameterPropertyPath = null;
Type parameterType = ModifierType?.ParameterType ?? DirectDataBinding.DataBinding.GetTargetType();
// If not null ensure the types match and if not, convert it
if (staticValue != null && staticValue.GetType() == parameterType)
ParameterStaticValue = staticValue;
else if (staticValue != null)
ParameterStaticValue = Convert.ChangeType(staticValue, parameterType);
// If null create a default instance for value types or simply make it null for reference types
else if (parameterType.IsValueType)
ParameterStaticValue = Activator.CreateInstance(parameterType);
else
ParameterStaticValue = null;
CreateExpression();
}
private void Initialize()
{
DataBindingModifierTypeStore.DataBindingModifierAdded += DataBindingModifierTypeStoreOnDataBindingModifierAdded;
DataBindingModifierTypeStore.DataBindingModifierRemoved += DataBindingModifierTypeStoreOnDataBindingModifierRemoved;
DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded;
DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved;
// Modifier type
if (Entity.ModifierTypePluginGuid != null && ModifierType == null)
{
DataBindingModifierType modifierType = DataBindingModifierTypeStore.Get(Entity.ModifierTypePluginGuid.Value, Entity.ModifierType)?.DataBindingModifierType;
if (modifierType != null)
UpdateModifierType(modifierType);
}
// Dynamic parameter
if (ParameterType == ProfileRightSideType.Dynamic && Entity.ParameterDataModelGuid != null && ParameterDataModel == null)
{
DataModel dataModel = DataModelStore.Get(Entity.ParameterDataModelGuid.Value)?.DataModel;
if (dataModel != null && dataModel.ContainsPath(Entity.ParameterPropertyPath))
UpdateParameter(dataModel, Entity.ParameterPropertyPath);
}
// Static parameter
else if (ParameterType == ProfileRightSideType.Static && Entity.ParameterStaticValue != null && ParameterStaticValue == null)
{
// Use the target type so JSON.NET has a better idea what to do
Type parameterType = ModifierType?.ParameterType ?? DirectDataBinding.DataBinding.GetTargetType();
object staticValue;
try
{
staticValue = JsonConvert.DeserializeObject(Entity.ParameterStaticValue, parameterType);
}
// If deserialization fails, use the type's default
catch (JsonSerializationException e)
{
DeserializationLogger.LogModifierDeserializationFailure(GetType().Name, e);
staticValue = Activator.CreateInstance(parameterType);
}
UpdateParameter(staticValue);
}
}
private void CreateExpression()
{
CompiledParameterAccessor = null;
if (ModifierType == null)
return;
if (ParameterType == ProfileRightSideType.Dynamic && ModifierType.SupportsParameter)
{
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
Expression parameterAccessor = ExpressionUtilities.CreateDataModelAccessor(
ParameterDataModel, ParameterPropertyPath, "parameter", out ParameterExpression rightSideParameter
);
Expression<Func<DataModel, object>> lambda = Expression.Lambda<Func<DataModel, object>>(Expression.Convert(parameterAccessor, typeof(object)), rightSideParameter);
CompiledParameterAccessor = lambda.Compile();
}
} }
#region Event handlers #region Event handlers
@ -325,21 +272,6 @@ namespace Artemis.Core
UpdateModifierType(null); UpdateModifierType(null);
} }
private void DataModelStoreOnDataModelAdded(object sender, DataModelStoreEvent e)
{
DataModel dataModel = e.Registration.DataModel;
if (dataModel.PluginInfo.Guid == Entity.ParameterDataModelGuid && dataModel.ContainsPath(Entity.ParameterPropertyPath))
UpdateParameter(dataModel, Entity.ParameterPropertyPath);
}
private void DataModelStoreOnDataModelRemoved(object sender, DataModelStoreEvent e)
{
if (e.Registration.DataModel != ParameterDataModel)
return;
ParameterDataModel = null;
CompiledParameterAccessor = null;
}
#endregion #endregion
} }
} }

View File

@ -1,8 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Artemis.Core.DataModelExpansions;
using Artemis.Storage.Entities.Profile.DataBindings; using Artemis.Storage.Entities.Profile.DataBindings;
namespace Artemis.Core namespace Artemis.Core
@ -20,31 +17,20 @@ namespace Artemis.Core
DataBinding = dataBinding; DataBinding = dataBinding;
Entity = entity; Entity = entity;
Initialize();
Load(); Load();
} }
internal DirectDataBindingEntity Entity { get; }
/// <summary> /// <summary>
/// Gets the currently used instance of the data model that contains the source of the data binding /// Gets the path of the source property
/// </summary> /// </summary>
public DataModel SourceDataModel { get; private set; } public DataModelPath? SourcePath { get; private set; }
/// <summary>
/// Gets the path of the source property in the <see cref="SourceDataModel" />
/// </summary>
public string SourcePropertyPath { get; private set; }
/// <summary> /// <summary>
/// Gets a list of modifiers applied to this data binding /// Gets a list of modifiers applied to this data binding
/// </summary> /// </summary>
public IReadOnlyList<DataBindingModifier<TLayerProperty, TProperty>> Modifiers => _modifiers.AsReadOnly(); public IReadOnlyList<DataBindingModifier<TLayerProperty, TProperty>> Modifiers => _modifiers.AsReadOnly();
/// <summary> internal DirectDataBindingEntity Entity { get; }
/// Gets the compiled function that gets the current value of the data binding target
/// </summary>
public Func<DataModel, object> CompiledTargetAccessor { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public DataBinding<TLayerProperty, TProperty> DataBinding { get; } public DataBinding<TLayerProperty, TProperty> DataBinding { get; }
@ -55,10 +41,10 @@ namespace Artemis.Core
if (_disposed) if (_disposed)
throw new ObjectDisposedException("DirectDataBinding"); throw new ObjectDisposedException("DirectDataBinding");
if (CompiledTargetAccessor == null) if (SourcePath == null || !SourcePath.IsValid)
return baseValue; return baseValue;
object dataBindingValue = CompiledTargetAccessor(SourceDataModel); object? dataBindingValue = SourcePath.GetValue();
foreach (DataBindingModifier<TLayerProperty, TProperty> dataBindingModifier in Modifiers) foreach (DataBindingModifier<TLayerProperty, TProperty> dataBindingModifier in Modifiers)
dataBindingValue = dataBindingModifier.Apply(dataBindingValue); dataBindingValue = dataBindingModifier.Apply(dataBindingValue);
@ -72,11 +58,10 @@ namespace Artemis.Core
{ {
_disposed = true; _disposed = true;
DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded;
DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved;
foreach (DataBindingModifier<TLayerProperty, TProperty> dataBindingModifier in Modifiers) foreach (DataBindingModifier<TLayerProperty, TProperty> dataBindingModifier in Modifiers)
dataBindingModifier.Dispose(); dataBindingModifier.Dispose();
SourcePath?.Dispose();
} }
#endregion #endregion
@ -86,7 +71,9 @@ namespace Artemis.Core
/// <inheritdoc /> /// <inheritdoc />
public void Load() public void Load()
{ {
// Data model is done during Initialize // Source
if (Entity.SourcePath != null)
SourcePath = new DataModelPath(null, Entity.SourcePath);
// Modifiers // Modifiers
foreach (DataBindingModifierEntity dataBindingModifierEntity in Entity.Modifiers) foreach (DataBindingModifierEntity dataBindingModifierEntity in Entity.Modifiers)
@ -98,12 +85,12 @@ namespace Artemis.Core
/// <inheritdoc /> /// <inheritdoc />
public void Save() public void Save()
{ {
// Data model // Don't save an invalid state
if (SourceDataModel != null) if (SourcePath != null && !SourcePath.IsValid)
{ return;
Entity.SourceDataModelGuid = SourceDataModel.PluginInfo.Guid;
Entity.SourcePropertyPath = SourcePropertyPath; SourcePath?.Save();
} Entity.SourcePath = SourcePath?.Entity;
// Modifiers // Modifiers
Entity.Modifiers.Clear(); Entity.Modifiers.Clear();
@ -113,76 +100,30 @@ namespace Artemis.Core
#endregion #endregion
#region Initialization
private void Initialize()
{
DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded;
DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved;
// Source
if (Entity.SourceDataModelGuid != null && SourceDataModel == null)
{
DataModel dataModel = DataModelStore.Get(Entity.SourceDataModelGuid.Value)?.DataModel;
if (dataModel != null && dataModel.ContainsPath(Entity.SourcePropertyPath))
UpdateSource(dataModel, Entity.SourcePropertyPath);
}
}
private void CreateExpression()
{
Type listType = SourceDataModel.GetListTypeInPath(SourcePropertyPath);
if (listType != null)
throw new ArtemisCoreException($"Cannot create a regular accessor at path {SourcePropertyPath} because the path contains a list");
ParameterExpression parameter = Expression.Parameter(typeof(DataModel), "targetDataModel");
Expression accessor = SourcePropertyPath.Split('.').Aggregate<string, Expression>(
Expression.Convert(parameter, SourceDataModel.GetType()), // Cast to the appropriate type
Expression.Property
);
UnaryExpression returnValue = Expression.Convert(accessor, typeof(object));
Expression<Func<DataModel, object>> lambda = Expression.Lambda<Func<DataModel, object>>(returnValue, parameter);
CompiledTargetAccessor = lambda.Compile();
}
#endregion
#region Source #region Source
/// <summary> /// <summary>
/// Returns the type of the source property of this data binding /// Returns the type of the source property of this data binding
/// </summary> /// </summary>
public Type GetSourceType() public Type? GetSourceType()
{ {
return SourceDataModel?.GetTypeAtPath(SourcePropertyPath); return SourcePath?.GetPropertyType();
} }
/// <summary> /// <summary>
/// Updates the source of the data binding and re-compiles the expression /// Updates the source of the data binding
/// </summary> /// </summary>
/// <param name="dataModel">The data model of the source</param> /// <param name="path">The path pointing to the source</param>
/// <param name="path">The path pointing to the source inside the data model</param> public void UpdateSource(DataModelPath? path)
public void UpdateSource(DataModel dataModel, string path)
{ {
if (_disposed) if (_disposed)
throw new ObjectDisposedException("DirectDataBinding"); throw new ObjectDisposedException("DirectDataBinding");
if (dataModel != null && path == null) if (path != null && !path.IsValid)
throw new ArtemisCoreException("If a data model is provided, a path is also required"); throw new ArtemisCoreException("Cannot update source of data binding to an invalid path");
if (dataModel == null && path != null)
throw new ArtemisCoreException("If path is provided, a data model is also required");
if (dataModel != null) SourcePath?.Dispose();
{ SourcePath = path != null ? new DataModelPath(path) : null;
if (!dataModel.ContainsPath(path))
throw new ArtemisCoreException($"Data model of type {dataModel.GetType().Name} does not contain a property at path '{path}'");
}
SourceDataModel = dataModel;
SourcePropertyPath = path;
CreateExpression();
} }
#endregion #endregion
@ -239,31 +180,12 @@ namespace Artemis.Core
#endregion #endregion
#region Event handlers
private void DataModelStoreOnDataModelAdded(object sender, DataModelStoreEvent e)
{
DataModel dataModel = e.Registration.DataModel;
if (dataModel.PluginInfo.Guid == Entity.SourceDataModelGuid && dataModel.ContainsPath(Entity.SourcePropertyPath))
UpdateSource(dataModel, Entity.SourcePropertyPath);
}
private void DataModelStoreOnDataModelRemoved(object sender, DataModelStoreEvent e)
{
if (SourceDataModel != e.Registration.DataModel)
return;
SourceDataModel = null;
CompiledTargetAccessor = null;
}
#endregion
#region Events #region Events
/// <summary> /// <summary>
/// Occurs when a modifier is added or removed /// Occurs when a modifier is added or removed
/// </summary> /// </summary>
public event EventHandler ModifiersUpdated; public event EventHandler? ModifiersUpdated;
protected virtual void OnModifiersUpdated() protected virtual void OnModifiersUpdated()
{ {

View File

@ -10,8 +10,7 @@ namespace Artemis.Storage.Entities.Profile.DataBindings
public int Order { get; set; } public int Order { get; set; }
public int ParameterType { get; set; } public int ParameterType { get; set; }
public Guid? ParameterDataModelGuid { get; set; } public DataModelPathEntity ParameterPath { get; set; }
public string ParameterPropertyPath { get; set; }
// Stored as a string to be able to control serialization and deserialization ourselves // Stored as a string to be able to control serialization and deserialization ourselves
public string ParameterStaticValue { get; set; } public string ParameterStaticValue { get; set; }

View File

@ -1,5 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
namespace Artemis.Storage.Entities.Profile.DataBindings namespace Artemis.Storage.Entities.Profile.DataBindings
{ {
@ -10,9 +9,7 @@ namespace Artemis.Storage.Entities.Profile.DataBindings
Modifiers = new List<DataBindingModifierEntity>(); Modifiers = new List<DataBindingModifierEntity>();
} }
public Guid? SourceDataModelGuid { get; set; } public DataModelPathEntity SourcePath { get; set; }
public string SourcePropertyPath { get; set; }
public List<DataBindingModifierEntity> Modifiers { get; set; } public List<DataBindingModifierEntity> Modifiers { get; set; }
} }
} }

View File

@ -20,10 +20,10 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
private bool _isEasingTimeEnabled; private bool _isEasingTimeEnabled;
private DataBindingModeType _selectedDataBindingMode; private DataBindingModeType _selectedDataBindingMode;
private TimelineEasingViewModel _selectedEasingViewModel; private TimelineEasingViewModel _selectedEasingViewModel;
private bool _updating;
private bool _applyTestResultToLayer; private bool _applyTestResultToLayer;
private bool _updating;
private bool _updatingTestResult;
public DataBindingViewModel(DataBindingRegistration<TLayerProperty, TProperty> registration, public DataBindingViewModel(DataBindingRegistration<TLayerProperty, TProperty> registration,
IProfileEditorService profileEditorService, IProfileEditorService profileEditorService,
@ -74,7 +74,11 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
public bool ApplyTestResultToLayer public bool ApplyTestResultToLayer
{ {
get => _applyTestResultToLayer; get => _applyTestResultToLayer;
set => SetAndNotify(ref _applyTestResultToLayer, value); set
{
if (!SetAndNotify(ref _applyTestResultToLayer, value)) return;
_profileEditorService.UpdateProfilePreview();
}
} }
public TimelineEasingViewModel SelectedEasingViewModel public TimelineEasingViewModel SelectedEasingViewModel
@ -111,6 +115,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
{ {
_updateTimer.Dispose(); _updateTimer.Dispose();
_updateTimer.Elapsed -= OnUpdateTimerOnElapsed; _updateTimer.Elapsed -= OnUpdateTimerOnElapsed;
Registration.LayerProperty.Updated -= LayerPropertyOnUpdated;
} }
private void Initialize() private void Initialize()
@ -119,6 +125,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
_updateTimer.Start(); _updateTimer.Start();
_updateTimer.Elapsed += OnUpdateTimerOnElapsed; _updateTimer.Elapsed += OnUpdateTimerOnElapsed;
Registration.LayerProperty.Updated += LayerPropertyOnUpdated;
CreateDataBindingModeModeViewModel(); CreateDataBindingModeModeViewModel();
Update(); Update();
@ -216,14 +223,22 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
private void UpdateTestResult() private void UpdateTestResult()
{ {
if (_updating || _updatingTestResult)
return;
_updatingTestResult = true;
if (Registration.DataBinding == null || ActiveItem == null) if (Registration.DataBinding == null || ActiveItem == null)
{ {
TestInputValue.UpdateValue(default); TestInputValue.UpdateValue(default);
TestResultValue.UpdateValue(default); TestResultValue.UpdateValue(default);
_updatingTestResult = false;
return; return;
} }
// While playing in preview data bindings aren't updated
Registration.DataBinding.Update(0.04); Registration.DataBinding.Update(0.04);
if (ActiveItem.SupportsTestValue) if (ActiveItem.SupportsTestValue)
{ {
TProperty currentValue = Registration.Converter.ConvertFromObject(ActiveItem?.GetTestValue() ?? default(TProperty)); TProperty currentValue = Registration.Converter.ConvertFromObject(ActiveItem?.GetTestValue() ?? default(TProperty));
@ -235,11 +250,15 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
if (ApplyTestResultToLayer) if (ApplyTestResultToLayer)
{ {
Registration.DataBinding.Apply(); // TODO: A bit crappy, the ProfileEditorService should really be doing this
_profileEditorService.UpdateProfilePreview(); bool playing = ((Parent as Screen)?.Parent as LayerPropertiesViewModel)?.Playing ?? true;
if (!playing)
_profileEditorService.UpdateProfilePreview();
} }
_updatingTestResult = false;
} }
private void EnableDataBinding() private void EnableDataBinding()
{ {
if (Registration.DataBinding != null) if (Registration.DataBinding != null)
@ -264,6 +283,12 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
{ {
UpdateTestResult(); UpdateTestResult();
} }
private void LayerPropertyOnUpdated(object? sender, LayerPropertyEventArgs<TLayerProperty> e)
{
if (ApplyTestResultToLayer)
Registration.DataBinding?.Apply();
}
} }
public interface IDataBindingViewModel : IScreen, IDisposable public interface IDataBindingViewModel : IScreen, IDisposable

View File

@ -94,10 +94,10 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDa
if (Modifier.ParameterType == ProfileRightSideType.Dynamic) if (Modifier.ParameterType == ProfileRightSideType.Dynamic)
{ {
Type sourceType = Modifier.DirectDataBinding.GetSourceType(); Type sourceType = Modifier.DirectDataBinding.GetSourceType();
Modifier.UpdateParameter((Modifier.ModifierType.ParameterType ?? sourceType).GetDefault()); Modifier.UpdateParameterStatic((Modifier.ModifierType.ParameterType ?? sourceType).GetDefault());
} }
else else
Modifier.UpdateParameter(null, null); Modifier.UpdateParameterDynamic(null);
Update(); Update();
_profileEditorService.UpdateSelectedProfileElement(); _profileEditorService.UpdateSelectedProfileElement();
@ -105,13 +105,13 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDa
private void ParameterSelectionViewModelOnPropertySelected(object sender, DataModelInputDynamicEventArgs e) private void ParameterSelectionViewModelOnPropertySelected(object sender, DataModelInputDynamicEventArgs e)
{ {
Modifier.UpdateParameter(e.DataModelPath.Target, e.DataModelPath.Path); Modifier.UpdateParameterDynamic(e.DataModelPath);
_profileEditorService.UpdateSelectedProfileElement(); _profileEditorService.UpdateSelectedProfileElement();
} }
private void StaticInputViewModelOnValueUpdated(object sender, DataModelInputStaticEventArgs e) private void StaticInputViewModelOnValueUpdated(object sender, DataModelInputStaticEventArgs e)
{ {
Modifier.UpdateParameter(e.Value); Modifier.UpdateParameterStatic(e.Value);
_profileEditorService.UpdateSelectedProfileElement(); _profileEditorService.UpdateSelectedProfileElement();
} }
@ -166,11 +166,10 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDa
SelectedModifierType = Modifier.ModifierType; SelectedModifierType = Modifier.ModifierType;
// Parameter // Parameter
throw new NotImplementedException(); if (DynamicSelectionViewModel != null)
// if (DynamicSelectionViewModel != null) DynamicSelectionViewModel.ChangeDataModelPath(Modifier.ParameterPath);
// DynamicSelectionViewModel.PopulateSelectedPropertyViewModel(Modifier.ParameterDataModel, Modifier.ParameterPropertyPath); else if (StaticInputViewModel != null)
// else if (StaticInputViewModel != null) StaticInputViewModel.Value = Modifier.ParameterStaticValue;
// StaticInputViewModel.Value = Modifier.ParameterStaticValue;
} }
private void ExecuteSelectModifierTypeCommand(object context) private void ExecuteSelectModifierTypeCommand(object context)

View File

@ -56,18 +56,16 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDa
public void Update() public void Update()
{ {
throw new NotImplementedException(); TargetSelectionViewModel.ChangeDataModelPath(DirectDataBinding.SourcePath);
// TargetSelectionViewModel.PopulateSelectedPropertyViewModel(DirectDataBinding.SourceDataModel, DirectDataBinding.SourcePropertyPath);
TargetSelectionViewModel.FilterTypes = new[] {DirectDataBinding.DataBinding.GetTargetType()}; TargetSelectionViewModel.FilterTypes = new[] {DirectDataBinding.DataBinding.GetTargetType()};
CanAddModifier = DirectDataBinding.SourceDataModel != null; CanAddModifier = DirectDataBinding.SourcePath != null && DirectDataBinding.SourcePath.IsValid;
UpdateModifierViewModels(); UpdateModifierViewModels();
} }
public object GetTestValue() public object GetTestValue()
{ {
throw new NotImplementedException(); return DirectDataBinding.SourcePath?.GetValue();
// return TargetSelectionViewModel.SelectedPropertyViewModel?.GetCurrentValue();
} }
#region IDisposable #region IDisposable
@ -114,8 +112,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDa
private void TargetSelectionViewModelOnPropertySelected(object sender, DataModelInputDynamicEventArgs e) private void TargetSelectionViewModelOnPropertySelected(object sender, DataModelInputDynamicEventArgs e)
{ {
throw new NotImplementedException(); DirectDataBinding.UpdateSource(e.DataModelPath);
// DirectDataBinding.UpdateSource(e.DataModelVisualizationViewModel.DataModel, e.DataModelVisualizationViewModel.Path);
Update(); Update();
_profileEditorService.UpdateSelectedProfileElement(); _profileEditorService.UpdateSelectedProfileElement();
@ -143,6 +140,10 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDa
modifierViewModel.Dispose(); modifierViewModel.Dispose();
} }
// Don't create children without a source or with an invalid source
if (DirectDataBinding.SourcePath == null || !DirectDataBinding.SourcePath.IsValid)
return;
// Add missing VMs // Add missing VMs
foreach (DataBindingModifier<TLayerProperty, TProperty> modifier in DirectDataBinding.Modifiers) foreach (DataBindingModifier<TLayerProperty, TProperty> modifier in DirectDataBinding.Modifiers)
{ {