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

Data bindings - Added the option for different data binding modes

This commit is contained in:
Robert 2020-09-15 19:40:57 +02:00
parent 906efdac98
commit f270d786f0
32 changed files with 940 additions and 497 deletions

View File

@ -16,6 +16,7 @@
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdatabindings_005Cconverters_005Cinternal/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdatabindings_005Cdatabindingproperties/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdatabindings_005Cdatabindingproperties_005Cabstract/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdatabindings_005Cmodes/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdatabindings_005Cmodifiers/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdatabindings_005Cmodifiers_005Cabstract/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Clayerproperties/@EntryIndexedValue">True</s:Boolean>

View File

@ -1,8 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Artemis.Core.DataModelExpansions;
using Artemis.Storage.Entities.Profile.DataBindings;
namespace Artemis.Core
@ -24,7 +21,7 @@ namespace Artemis.Core
ApplyRegistration(dataBindingRegistration);
Save();
Initialize();
ApplyDataBindingMode();
}
internal DataBinding(LayerProperty<TLayerProperty> layerProperty, DataBindingEntity entity)
@ -33,8 +30,8 @@ namespace Artemis.Core
Entity = entity;
// Load will add children so be initialized before that
Initialize();
Load();
ApplyDataBindingMode();
}
/// <summary>
@ -53,16 +50,9 @@ namespace Artemis.Core
public DataBindingConverter<TLayerProperty, TProperty> Converter { get; private set; }
/// <summary>
/// Gets the currently used instance of the data model that contains the source of the data binding
/// Gets the data binding mode
/// </summary>
public DataModel SourceDataModel { get; private set; }
/// <summary>
/// Gets the path of the source property in the <see cref="SourceDataModel" />
/// </summary>
public string SourcePropertyPath { get; private set; }
public DataBindingMode Mode { get; set; }
public IDataBindingMode<TLayerProperty, TProperty> DataBindingMode { get; private set; }
/// <summary>
/// Gets or sets the easing time of the data binding
@ -74,15 +64,6 @@ namespace Artemis.Core
/// </summary>
public Easings.Functions EasingFunction { get; set; }
/// <summary>
/// Gets a list of modifiers applied to this data binding
/// </summary>
public IReadOnlyList<DataBindingModifier<TLayerProperty, TProperty>> Modifiers => _modifiers.AsReadOnly();
/// <summary>
/// Gets the compiled function that gets the current value of the data binding target
/// </summary>
public Func<DataModel, object> CompiledTargetAccessor { get; private set; }
internal DataBindingEntity Entity { get; }
@ -122,72 +103,9 @@ namespace Artemis.Core
{
_disposed = true;
DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded;
DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved;
foreach (var dataBindingModifier in Modifiers)
dataBindingModifier.Dispose();
DataBindingMode?.Dispose();
}
/// <summary>
/// Adds a modifier to the data binding's <see cref="Modifiers" /> collection
/// </summary>
public void AddModifier(DataBindingModifier<TLayerProperty, TProperty> modifier)
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
if (!_modifiers.Contains(modifier))
{
modifier.DataBinding = this;
_modifiers.Add(modifier);
OnModifiersUpdated();
}
}
/// <summary>
/// Removes a modifier from the data binding's <see cref="Modifiers" /> collection
/// </summary>
public void RemoveModifier(DataBindingModifier<TLayerProperty, TProperty> modifier)
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
if (_modifiers.Contains(modifier))
{
modifier.DataBinding = null;
_modifiers.Remove(modifier);
OnModifiersUpdated();
}
}
/// <summary>
/// Updates the source of the data binding and re-compiles the expression
/// </summary>
/// <param name="dataModel">The data model of the source</param>
/// <param name="path">The path pointing to the source inside the data model</param>
public void UpdateSource(DataModel dataModel, string path)
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
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}'");
}
SourceDataModel = dataModel;
SourcePropertyPath = path;
CreateExpression();
}
/// <summary>
/// Gets the current value of the data binding
@ -199,15 +117,11 @@ namespace Artemis.Core
if (_disposed)
throw new ObjectDisposedException("DataBinding");
if (CompiledTargetAccessor == null || Converter == null)
if (Converter == null || DataBindingMode == null)
return baseValue;
var dataBindingValue = CompiledTargetAccessor(SourceDataModel);
foreach (var dataBindingModifier in Modifiers)
dataBindingValue = dataBindingModifier.Apply(dataBindingValue);
var value = DataBindingMode.GetValue(baseValue);
TProperty value = Converter.ConvertFromObject(dataBindingValue);
// If no easing is to be applied simple return whatever the current value is
if (EasingTime == TimeSpan.Zero || !Converter.SupportsInterpolate)
return value;
@ -228,28 +142,6 @@ namespace Artemis.Core
return Registration.PropertyExpression.ReturnType;
}
/// <summary>
/// Returns the type of the source property of this data binding
/// </summary>
public Type GetSourceType()
{
return SourceDataModel?.GetTypeAtPath(SourcePropertyPath);
}
private void Initialize()
{
DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded;
DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved;
// Source
if (Entity.SourceDataModelGuid != null && SourceDataModel == null)
{
var dataModel = DataModelStore.Get(Entity.SourceDataModelGuid.Value)?.DataModel;
if (dataModel != null && dataModel.ContainsPath(Entity.SourcePropertyPath))
UpdateSource(dataModel, Entity.SourcePropertyPath);
}
}
private void ResetEasing(TProperty value)
{
_previousValue = GetInterpolatedValue();
@ -284,25 +176,48 @@ namespace Artemis.Core
var easingAmount = _easingProgress.TotalSeconds / EasingTime.TotalSeconds;
return Converter.Interpolate(_previousValue, _currentValue, Easings.Interpolate(easingAmount, EasingFunction));
}
#region Mode management
private void CreateExpression()
/// <summary>
/// Changes the data binding mode of the data binding to the specified <paramref name="dataBindingMode" />
/// </summary>
public void ChangeDataBindingMode(DataBindingModeType dataBindingMode)
{
var listType = SourceDataModel.GetListTypeInPath(SourcePropertyPath);
if (listType != null)
throw new ArtemisCoreException($"Cannot create a regular accessor at path {SourcePropertyPath} because the path contains a list");
switch (dataBindingMode)
{
case DataBindingModeType.Direct:
Entity.DataBindingMode = new DirectDataBindingEntity();
break;
case DataBindingModeType.Conditional:
Entity.DataBindingMode = new ConditionalDataBindingEntity();
break;
default:
Entity.DataBindingMode = null;
break;
}
var parameter = Expression.Parameter(typeof(DataModel), "targetDataModel");
var accessor = SourcePropertyPath.Split('.').Aggregate<string, Expression>(
Expression.Convert(parameter, SourceDataModel.GetType()), // Cast to the appropriate type
Expression.Property
);
var returnValue = Expression.Convert(accessor, typeof(object));
var lambda = Expression.Lambda<Func<DataModel, object>>(returnValue, parameter);
CompiledTargetAccessor = lambda.Compile();
ApplyDataBindingMode();
}
private void ApplyDataBindingMode()
{
DataBindingMode?.Dispose();
DataBindingMode = null;
switch (Entity.DataBindingMode)
{
case DirectDataBindingEntity directDataBindingEntity:
DataBindingMode = new DirectDataBinding<TLayerProperty, TProperty>(this, directDataBindingEntity);
break;
case ConditionalDataBindingEntity conditionalDataBindingEntity:
DataBindingMode = new ConditionalDataBinding<TLayerProperty, TProperty>(this, conditionalDataBindingEntity);
break;
}
}
#endregion
#region Storage
/// <inheritdoc />
@ -314,15 +229,10 @@ namespace Artemis.Core
var registration = LayerProperty.GetDataBindingRegistration<TProperty>(Entity.TargetExpression);
ApplyRegistration(registration);
Mode = (DataBindingMode) Entity.DataBindingMode;
EasingTime = Entity.EasingTime;
EasingFunction = (Easings.Functions) Entity.EasingFunction;
// Data model is done during Initialize
// Modifiers
foreach (var dataBindingModifierEntity in Entity.Modifiers)
_modifiers.Add(new DataBindingModifier<TLayerProperty, TProperty>(this, dataBindingModifierEntity));
DataBindingMode?.Load();
}
/// <inheritdoc />
@ -336,54 +246,10 @@ namespace Artemis.Core
// General
Entity.TargetExpression = Registration.PropertyExpression.ToString();
Entity.DataBindingMode = (int) Mode;
Entity.EasingTime = EasingTime;
Entity.EasingFunction = (int) EasingFunction;
// Data model
if (SourceDataModel != null)
{
Entity.SourceDataModelGuid = SourceDataModel.PluginInfo.Guid;
Entity.SourcePropertyPath = SourcePropertyPath;
}
// Modifiers
Entity.Modifiers.Clear();
foreach (var dataBindingModifier in Modifiers)
dataBindingModifier.Save();
}
#endregion
#region Event handlers
private void DataModelStoreOnDataModelAdded(object sender, DataModelStoreEvent e)
{
var 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
/// <summary>
/// Occurs when a modifier is added or removed
/// </summary>
public event EventHandler ModifiersUpdated;
protected virtual void OnModifiersUpdated()
{
ModifiersUpdated?.Invoke(this, EventArgs.Empty);
DataBindingMode?.Save();
}
#endregion
@ -392,16 +258,21 @@ namespace Artemis.Core
/// <summary>
/// A mode that determines how the data binding is applied to the layer property
/// </summary>
public enum DataBindingMode
public enum DataBindingModeType
{
/// <summary>
/// Disables the data binding
/// </summary>
None,
/// <summary>
/// Replaces the layer property value with the data binding value
/// </summary>
Replace,
Direct,
/// <summary>
/// Adds the data binding value to the layer property value
/// Replaces the layer property value with the data binding value
/// </summary>
Add
Conditional,
}
}

View File

@ -10,49 +10,40 @@ namespace Artemis.Core
/// <inheritdoc />
public class DataBindingModifier<TLayerProperty, TProperty> : IDataBindingModifier
{
private DataBinding<TLayerProperty, TProperty> _dataBinding;
private bool _disposed;
/// <summary>
/// Creates a new instance of the <see cref="DataBindingModifier{TLayerProperty,TProperty}" /> class
/// </summary>
/// <param name="dataBinding">The data binding the modifier is to be applied to</param>
/// <param name="directDataBinding">The direct data binding the modifier is to be applied to</param>
/// <param name="parameterType">The type of the parameter, can either be dynamic (based on a data model value) or static</param>
public DataBindingModifier(DataBinding<TLayerProperty, TProperty> dataBinding, ProfileRightSideType parameterType)
public DataBindingModifier(DirectDataBinding<TLayerProperty, TProperty> directDataBinding, ProfileRightSideType parameterType)
{
_dataBinding = dataBinding ?? throw new ArgumentNullException(nameof(dataBinding));
DirectDataBinding = directDataBinding ?? throw new ArgumentNullException(nameof(directDataBinding));
ParameterType = parameterType;
Entity = new DataBindingModifierEntity();
Initialize();
Save();
}
internal DataBindingModifier(DataBinding<TLayerProperty, TProperty> dataBinding, DataBindingModifierEntity entity)
internal DataBindingModifier(DirectDataBinding<TLayerProperty, TProperty> directDataBinding, DataBindingModifierEntity entity)
{
_dataBinding = dataBinding;
DirectDataBinding = directDataBinding ?? throw new ArgumentNullException(nameof(directDataBinding));
Entity = entity;
Load();
Initialize();
}
/// <summary>
/// Gets the data binding this modifier is applied to
/// </summary>
public DataBinding<TLayerProperty, TProperty> DataBinding
{
get => _dataBinding;
internal set
{
_dataBinding = value;
CreateExpression();
}
}
/// <summary>
/// Gets the type of modifier that is being applied
/// </summary>
public DataBindingModifierType ModifierType { get; private set; }
/// <summary>
/// Gets the direct data binding this modifier is applied to
/// </summary>
public DirectDataBinding<TLayerProperty, TProperty> DirectDataBinding { get; }
/// <summary>
/// Gets the type of the parameter, can either be dynamic (based on a data model value) or static
/// </summary>
@ -93,8 +84,8 @@ namespace Artemis.Core
if (_disposed)
throw new ObjectDisposedException("DataBindingModifier");
if (!DataBinding.Entity.Modifiers.Contains(Entity))
DataBinding.Entity.Modifiers.Add(Entity);
if (!DirectDataBinding.Entity.Modifiers.Contains(Entity))
DirectDataBinding.Entity.Modifiers.Add(Entity);
// Modifier
if (ModifierType != null)
@ -177,7 +168,7 @@ namespace Artemis.Core
return;
}
var targetType = DataBinding.GetTargetType();
var targetType = DirectDataBinding.DataBinding.GetTargetType();
if (!modifierType.SupportsType(targetType))
{
throw new ArtemisCoreException($"Cannot apply modifier type {modifierType.GetType().Name} to this modifier because " +
@ -229,7 +220,7 @@ namespace Artemis.Core
ParameterDataModel = null;
ParameterPropertyPath = null;
var targetType = DataBinding.GetTargetType();
var targetType = DirectDataBinding.DataBinding.GetTargetType();
// If not null ensure the types match and if not, convert it
if (staticValue != null && staticValue.GetType() == targetType)
@ -271,7 +262,7 @@ namespace Artemis.Core
else if (ParameterType == ProfileRightSideType.Static && Entity.ParameterStaticValue != null && ParameterStaticValue == null)
{
// Use the target type so JSON.NET has a better idea what to do
var targetType = DataBinding.GetTargetType();
var targetType = DirectDataBinding.DataBinding.GetTargetType();
object staticValue;
try
@ -293,7 +284,7 @@ namespace Artemis.Core
{
CompiledParameterAccessor = null;
if (ModifierType == null || DataBinding == null)
if (ModifierType == null)
return;
if (ParameterType == ProfileRightSideType.Dynamic && ModifierType.SupportsParameter)

View File

@ -0,0 +1,50 @@
using Artemis.Storage.Entities.Profile.DataBindings;
namespace Artemis.Core
{
/// <summary>
/// Represents a data binding mode that applies a value depending on conditions
/// </summary>
public class ConditionalDataBinding<TLayerProperty, TProperty> : IDataBindingMode<TLayerProperty, TProperty>
{
internal ConditionalDataBinding(DataBinding<TLayerProperty, TProperty> dataBinding, ConditionalDataBindingEntity entity)
{
DataBinding = dataBinding;
Entity = entity;
}
/// <inheritdoc />
public DataBinding<TLayerProperty, TProperty> DataBinding { get; }
/// <inheritdoc />
public TProperty GetValue(TProperty baseValue)
{
return default;
}
internal ConditionalDataBindingEntity Entity { get; }
#region Storage
/// <inheritdoc />
public void Load()
{
}
/// <inheritdoc />
public void Save()
{
}
#endregion
#region IDisposable
/// <inheritdoc />
public void Dispose()
{
}
#endregion
}
}

View File

@ -0,0 +1,255 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Artemis.Core.DataModelExpansions;
using Artemis.Storage.Entities.Profile.DataBindings;
namespace Artemis.Core
{
/// <summary>
/// Represents a data binding mode that directly applies a data model value to a data binding
/// </summary>
public class DirectDataBinding<TLayerProperty, TProperty> : IDataBindingMode<TLayerProperty, TProperty>
{
private readonly List<DataBindingModifier<TLayerProperty, TProperty>> _modifiers = new List<DataBindingModifier<TLayerProperty, TProperty>>();
private bool _disposed;
internal DirectDataBinding(DataBinding<TLayerProperty, TProperty> dataBinding, DirectDataBindingEntity entity)
{
DataBinding = dataBinding;
Entity = entity;
Initialize();
Load();
}
/// <inheritdoc />
public DataBinding<TLayerProperty, TProperty> DataBinding { get; }
internal DirectDataBindingEntity Entity { get; }
/// <summary>
/// Gets the currently used instance of the data model that contains the source of the data binding
/// </summary>
public DataModel SourceDataModel { get; private set; }
/// <summary>
/// Gets the path of the source property in the <see cref="SourceDataModel" />
/// </summary>
public string SourcePropertyPath { get; private set; }
/// <summary>
/// Gets a list of modifiers applied to this data binding
/// </summary>
public IReadOnlyList<DataBindingModifier<TLayerProperty, TProperty>> Modifiers => _modifiers.AsReadOnly();
/// <summary>
/// Gets the compiled function that gets the current value of the data binding target
/// </summary>
public Func<DataModel, object> CompiledTargetAccessor { get; private set; }
/// <inheritdoc />
public TProperty GetValue(TProperty baseValue)
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
if (CompiledTargetAccessor == null)
return baseValue;
var dataBindingValue = CompiledTargetAccessor(SourceDataModel);
foreach (var dataBindingModifier in Modifiers)
dataBindingValue = dataBindingModifier.Apply(dataBindingValue);
return DataBinding.Converter.ConvertFromObject(dataBindingValue);
}
#region IDisposable
/// <inheritdoc />
public void Dispose()
{
_disposed = true;
foreach (var dataBindingModifier in Modifiers)
dataBindingModifier.Dispose();
}
#endregion
#region Storage
/// <inheritdoc />
public void Load()
{
// Data model is done during Initialize
// Modifiers
foreach (var dataBindingModifierEntity in Entity.Modifiers)
_modifiers.Add(new DataBindingModifier<TLayerProperty, TProperty>(this, dataBindingModifierEntity));
}
/// <inheritdoc />
public void Save()
{
// Data model
if (SourceDataModel != null)
{
Entity.SourceDataModelGuid = SourceDataModel.PluginInfo.Guid;
Entity.SourcePropertyPath = SourcePropertyPath;
}
// Modifiers
Entity.Modifiers.Clear();
foreach (var dataBindingModifier in Modifiers)
dataBindingModifier.Save();
}
#endregion
#region Initialization
private void Initialize()
{
DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded;
DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved;
// Source
if (Entity.SourceDataModelGuid != null && SourceDataModel == null)
{
var dataModel = DataModelStore.Get(Entity.SourceDataModelGuid.Value)?.DataModel;
if (dataModel != null && dataModel.ContainsPath(Entity.SourcePropertyPath))
UpdateSource(dataModel, Entity.SourcePropertyPath);
}
}
private void CreateExpression()
{
var listType = SourceDataModel.GetListTypeInPath(SourcePropertyPath);
if (listType != null)
throw new ArtemisCoreException($"Cannot create a regular accessor at path {SourcePropertyPath} because the path contains a list");
var parameter = Expression.Parameter(typeof(DataModel), "targetDataModel");
var accessor = SourcePropertyPath.Split('.').Aggregate<string, Expression>(
Expression.Convert(parameter, SourceDataModel.GetType()), // Cast to the appropriate type
Expression.Property
);
var returnValue = Expression.Convert(accessor, typeof(object));
var lambda = Expression.Lambda<Func<DataModel, object>>(returnValue, parameter);
CompiledTargetAccessor = lambda.Compile();
}
#endregion
#region Source
/// <summary>
/// Returns the type of the source property of this data binding
/// </summary>
public Type GetSourceType()
{
return SourceDataModel?.GetTypeAtPath(SourcePropertyPath);
}
/// <summary>
/// Updates the source of the data binding and re-compiles the expression
/// </summary>
/// <param name="dataModel">The data model of the source</param>
/// <param name="path">The path pointing to the source inside the data model</param>
public void UpdateSource(DataModel dataModel, string path)
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
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}'");
}
SourceDataModel = dataModel;
SourcePropertyPath = path;
CreateExpression();
}
#endregion
#region Modifiers
/// <summary>
/// Adds a modifier to the data binding's <see cref="Modifiers" /> collection
/// </summary>
public DataBindingModifier<TLayerProperty, TProperty> AddModifier(ProfileRightSideType type)
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
var modifier = new DataBindingModifier<TLayerProperty, TProperty>(this, type);
_modifiers.Add(modifier);
OnModifiersUpdated();
return modifier;
}
/// <summary>
/// Removes a modifier from the data binding's <see cref="Modifiers" /> collection and disposes it
/// </summary>
public void RemoveModifier(DataBindingModifier<TLayerProperty, TProperty> modifier)
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
if (_modifiers.Contains(modifier))
{
_modifiers.Remove(modifier);
modifier.Dispose();
OnModifiersUpdated();
}
}
#endregion
#region Event handlers
private void DataModelStoreOnDataModelAdded(object sender, DataModelStoreEvent e)
{
var 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
/// <summary>
/// Occurs when a modifier is added or removed
/// </summary>
public event EventHandler ModifiersUpdated;
protected virtual void OnModifiersUpdated()
{
ModifiersUpdated?.Invoke(this, EventArgs.Empty);
}
#endregion
}
}

View File

@ -0,0 +1,22 @@
using System;
namespace Artemis.Core
{
/// <summary>
/// Represents a data binding mode
/// </summary>
public interface IDataBindingMode<TLayerProperty, TProperty> : IStorageModel, IDisposable
{
/// <summary>
/// Gets the data binding this mode is applied to
/// </summary>
DataBinding<TLayerProperty, TProperty> DataBinding { get; }
/// <summary>
/// Gets the current value of the data binding
/// </summary>
/// <param name="baseValue">The base value of the property the data binding is applied to</param>
/// <returns></returns>
TProperty GetValue(TProperty baseValue);
}
}

View File

@ -738,7 +738,7 @@ namespace Artemis.Core
private void LayerBrushStoreOnLayerBrushAdded(object sender, LayerBrushStoreEvent e)
{
if (LayerBrush != null)
if (LayerBrush != null || General.BrushReference?.CurrentValue == null)
return;
var current = General.BrushReference.CurrentValue;

View File

@ -7,8 +7,8 @@ namespace Artemis.Core
{
internal SKSizeLayerProperty()
{
RegisterDataBindingProperty(size => size.Width, new FloatDataBindingConverter<SKSize>());
RegisterDataBindingProperty(size => size.Height, new FloatDataBindingConverter<SKSize>());
RegisterDataBindingProperty(size => size.Width, new FloatDataBindingConverter<SKSize>());
}
/// <summary>

View File

@ -0,0 +1,14 @@
using System.Collections.Generic;
namespace Artemis.Storage.Entities.Profile.DataBindings
{
public class ConditionalDataBindingEntity : IDataBindingModeEntity
{
public ConditionalDataBindingEntity()
{
Values = new List<DataBindingConditionValueEntity>();
}
public List<DataBindingConditionValueEntity> Values { get; set; }
}
}

View File

@ -0,0 +1,10 @@
using Artemis.Storage.Entities.Profile.Conditions;
namespace Artemis.Storage.Entities.Profile.DataBindings
{
public class DataBindingConditionValueEntity
{
public string Value { get; set; }
public DisplayConditionGroupEntity RootGroup { get; set; }
}
}

View File

@ -1,23 +1,13 @@
using System;
using System.Collections.Generic;
namespace Artemis.Storage.Entities.Profile.DataBindings
{
public class DataBindingEntity
{
public DataBindingEntity()
{
Modifiers = new List<DataBindingModifierEntity>();
}
public string TargetExpression { get; set; }
public Guid? SourceDataModelGuid { get; set; }
public string SourcePropertyPath { get; set; }
public int DataBindingMode { get; set; }
public TimeSpan EasingTime { get; set; }
public int EasingFunction { get; set; }
public List<DataBindingModifierEntity> Modifiers { get; set; }
public IDataBindingModeEntity DataBindingMode { get; set; }
}
}

View File

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
namespace Artemis.Storage.Entities.Profile.DataBindings
{
public class DirectDataBindingEntity : IDataBindingModeEntity
{
public DirectDataBindingEntity()
{
Modifiers = new List<DataBindingModifierEntity>();
}
public Guid? SourceDataModelGuid { get; set; }
public string SourcePropertyPath { get; set; }
public List<DataBindingModifierEntity> Modifiers { get; set; }
}
}

View File

@ -0,0 +1,6 @@
namespace Artemis.Storage.Entities.Profile.DataBindings
{
public interface IDataBindingModeEntity
{
}
}

View File

@ -1,8 +1,8 @@
using System;
using System.Linq;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Migrations.Interfaces;
using LiteDB;
using System;
using System.Linq;
namespace Artemis.Storage.Migrations
{

View File

@ -0,0 +1,31 @@
using Artemis.Storage.Migrations.Interfaces;
using LiteDB;
namespace Artemis.Storage.Migrations
{
public class M5DataBindingTypes : IStorageMigration
{
public int UserVersion => 5;
public void Apply(LiteRepository repository)
{
var collection = repository.Database.GetCollection("ProfileEntity");
foreach (var bsonDocument in collection.FindAll())
{
foreach (var bsonLayer in bsonDocument["Layers"].AsArray)
{
foreach (var bsonPropertyEntity in bsonLayer["PropertyEntities"].AsArray)
bsonPropertyEntity["DataBindingEntities"].AsArray.Clear();
}
foreach (var bsonLayer in bsonDocument["Folders"].AsArray)
{
foreach (var bsonPropertyEntity in bsonLayer["PropertyEntities"].AsArray)
bsonPropertyEntity["DataBindingEntities"].AsArray.Clear();
}
collection.Update(bsonDocument);
}
}
}
}

View File

@ -6,6 +6,8 @@ using Artemis.UI.Screens.ProfileEditor;
using Artemis.UI.Screens.ProfileEditor.DisplayConditions;
using Artemis.UI.Screens.ProfileEditor.LayerProperties;
using Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings;
using Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.ConditionalDataBinding;
using Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDataBinding;
using Artemis.UI.Screens.ProfileEditor.LayerProperties.LayerEffects;
using Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline;
using Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree;
@ -87,6 +89,8 @@ namespace Artemis.UI.Ninject.Factories
{
IDataBindingViewModel DataBindingViewModel(IDataBindingRegistration registration);
DataBindingModifierViewModel<TLayerProperty, TProperty> DataBindingModifierViewModel<TLayerProperty, TProperty>(DataBindingModifier<TLayerProperty, TProperty> modifier);
DirectDataBindingModeViewModel<TLayerProperty, TProperty> DirectDataBindingModeViewModel<TLayerProperty, TProperty>(DirectDataBinding<TLayerProperty, TProperty> directDataBinding);
ConditionalDataBindingModeViewModel<TLayerProperty, TProperty> ConditionalDataBindingModeViewModel<TLayerProperty, TProperty>(ConditionalDataBinding<TLayerProperty, TProperty> conditionalDataBinding);
}
public interface IPropertyVmFactory

View File

@ -31,6 +31,29 @@
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Background="{StaticResource MaterialDesignCardBackground}">
<ContentControl s:View.Model="{Binding ActiveItem}" />
</ScrollViewer>
<materialDesign:Card
Visibility="{Binding DisplayStartHint, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}"
VerticalAlignment="Top"
Background="{DynamicResource PrimaryHueDarkBrush}"
Foreground="{DynamicResource PrimaryHueDarkForegroundBrush}"
Margin="5 30 5 0"
Padding="8"
Width="315"
HorizontalAlignment="Left">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<materialDesign:PackIcon Kind="Redo" Margin="0 0 5 0">
<materialDesign:PackIcon.RenderTransform>
<RotateTransform Angle="225" CenterX="8" CenterY="8" />
</materialDesign:PackIcon.RenderTransform>
</materialDesign:PackIcon>
<TextBlock Grid.Column="1" VerticalAlignment="Center">Click the plus icon to start using display conditions!</TextBlock>
</Grid>
</materialDesign:Card>
</Grid>
<Grid Grid.Row="3">

View File

@ -0,0 +1,20 @@
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.ConditionalDataBinding.ConditionalDataBindingModeView"
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"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Top" Margin="16">
<materialDesign:PackIcon Kind="Crane" Width="60" Height="60" HorizontalAlignment="Center" />
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}" TextWrapping="Wrap" HorizontalAlignment="Center" Margin="0 25">
Conditional data bindings not yet implemented
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}" TextWrapping="Wrap" HorizontalAlignment="Center">
This is where you'd provide values and their conditions.. :>
</TextBlock>
</StackPanel>
</Grid>
</UserControl>

View File

@ -0,0 +1,28 @@
using Artemis.Core;
using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.ConditionalDataBinding
{
public class ConditionalDataBindingModeViewModel<TLayerProperty, TProperty> : Screen, IDataBindingModeViewModel
{
public ConditionalDataBindingModeViewModel(ConditionalDataBinding<TLayerProperty, TProperty> conditionalDataBinding)
{
ConditionalDataBinding = conditionalDataBinding;
}
public ConditionalDataBinding<TLayerProperty, TProperty> ConditionalDataBinding { get; }
public void Dispose()
{
}
public void Update()
{
}
public object GetTestValue()
{
return null;
}
}
}

View File

@ -17,152 +17,112 @@
</UserControl.Resources>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<Grid Margin="10 0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.75*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Margin="0 5 5 0">
<Grid.RowDefinitions>
<RowDefinition Height="48" />
<RowDefinition Height="48" />
<RowDefinition Height="48" />
<RowDefinition Height="48" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Margin="-3 0 0 0">
<Label FontSize="10" Foreground="{StaticResource MaterialDesignNavigationItemSubheader}" Margin="0 0 0 2">Source</Label>
<ContentControl s:View.Model="{Binding TargetSelectionViewModel}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsTabStop="False" />
</StackPanel>
<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="48" />
<RowDefinition Height="48" />
<RowDefinition Height="48" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ComboBox Grid.Row="1"
<ComboBox Grid.Row="0"
Style="{StaticResource MaterialDesignFloatingHintComboBox}"
materialDesign:HintAssist.Hint="Data binding mode"
MinWidth="128"
SelectedValue="{Binding SelectedDataBindingMode}" ItemsSource="{Binding DataBindingModes}" SelectedValuePath="Value" DisplayMemberPath="Description" >
</ComboBox>
SelectedValue="{Binding SelectedDataBindingMode}"
ItemsSource="{Binding DataBindingModes}"
SelectedValuePath="Value"
DisplayMemberPath="Description" >
</ComboBox>
<StackPanel Grid.Row="2">
<TextBox Style="{StaticResource MaterialDesignFloatingHintTextBox}"
<StackPanel Grid.Row="1">
<TextBox Style="{StaticResource MaterialDesignFloatingHintTextBox}"
Text="{Binding EasingTime}"
materialDesign:TextFieldAssist.HasClearButton="True"
materialDesign:TextFieldAssist.SuffixText="ms"
materialDesign:HintAssist.Hint="Easing time"
IsEnabled="{Binding IsDataBindingEnabled}" />
</StackPanel>
</StackPanel>
<ComboBox Grid.Row="3"
<ComboBox Grid.Row="2"
Style="{StaticResource MaterialDesignFloatingHintComboBox}"
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}"
<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="4" Grid.ColumnSpan="2" Margin="0 5 0 0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Margin="10 10 4 0">
Test result
</TextBlock>
<Separator Grid.Row="1" Style="{StaticResource MaterialDesignLightSeparator}" Margin="0" />
<Grid Grid.Row="2" Margin="10 4 10 10">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Margin="0 2">Input</TextBlock>
<TextBlock Grid.Row="0" Grid.Column="1" Margin="0 2" FontFamily="Consolas" Text="{Binding TestInputValue}" />
<TextBlock Grid.Row="1" Grid.Column="0" Margin="0 2">Output</TextBlock>
<TextBlock Grid.Row="1" Grid.Column="1" Margin="0 2" FontFamily="Consolas" Text="{Binding TestResultValue}" />
</Grid>
</Grid>
</materialDesign:Card>
</Grid>
<Grid Grid.Column="1">
<Grid Margin="5 26 0 0">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button Style="{StaticResource MaterialDesignFlatMidBgButton}"
HorizontalAlignment="Left"
FontSize="12"
Padding="6 4"
Height="22"
IsEnabled="{Binding CanAddModifier}"
Command="{s:Action AddModifier}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Margin="0 -1 4 0" Kind="Plus" />
<TextBlock>
Add modifier
</TextBlock>
</StackPanel>
</Button>
<CheckBox Grid.Row="0"
Grid.Column="1"
Style="{StaticResource MaterialDesignCheckBox}"
HorizontalAlignment="Right"
VerticalAlignment="Top"
IsChecked="{Binding IsDataBindingEnabled}">
Enable data binding
</CheckBox>
<ListBox Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
ItemsSource="{Binding ModifierViewModels}"
materialDesign:RippleAssist.IsDisabled="True"
dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True"
dd:DragDrop.UseDefaultDragAdorner="True"
dd:DragDrop.DropHandler="{Binding}"
HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<ContentControl s:View.Model="{Binding}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsTabStop="False" />
<TextBlock Text="{Binding Description}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ComboBox.ItemTemplate>
</ComboBox>
<materialDesign:Card Grid.Row="3" Grid.ColumnSpan="2" Margin="0 5 0 0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Margin="10 10 4 0">
Test result
</TextBlock>
<Separator Grid.Row="1" Style="{StaticResource MaterialDesignLightSeparator}" Margin="0" />
<Grid Grid.Row="2" Margin="10 4 10 10">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Margin="0 2">Input</TextBlock>
<TextBlock Grid.Row="0" Grid.Column="1" Margin="0 2" FontFamily="Consolas" Text="{Binding TestInputValue}" />
<TextBlock Grid.Row="1" Grid.Column="0" Margin="0 2">Output</TextBlock>
<TextBlock Grid.Row="1" Grid.Column="1" Margin="0 2" FontFamily="Consolas" Text="{Binding TestResultValue}" />
</Grid>
</Grid>
</materialDesign:Card>
</Grid>
</Grid>
</materialDesign:Card>
<materialDesign:Card Grid.Column="1" UniformCornerRadius="0" Background="{DynamicResource MaterialDesignToolBarBackground}" Panel.ZIndex="1">
<Grid Margin="10 5">
<ContentControl s:View.Model="{Binding ActiveItem, IsAsync=True}"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch"
IsTabStop="False"/>
</Grid>
</materialDesign:Card>
</Grid>
</ScrollViewer>
</UserControl>

View File

@ -4,38 +4,32 @@ using Artemis.Core;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Input;
using Artemis.UI.Shared.Services;
using Ninject.Planning.Targets;
using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
{
public class DataBindingViewModel<TLayerProperty, TProperty> : Screen, IDataBindingViewModel
public class DataBindingViewModel<TLayerProperty, TProperty> : Conductor<IDataBindingModeViewModel>, IDataBindingViewModel
{
private readonly IDataBindingsVmFactory _dataBindingsVmFactory;
private readonly IDataModelUIService _dataModelUIService;
private readonly IProfileEditorService _profileEditorService;
private DataBinding<TLayerProperty, TProperty> _dataBinding;
private int _easingTime;
private bool _isDataBindingEnabled;
private bool _isEasingTimeEnabled;
private DataBindingMode _selectedDataBindingMode;
private DataBindingModeType _selectedDataBindingMode;
private TimelineEasingViewModel _selectedEasingViewModel;
private DataModelDynamicViewModel _targetSelectionViewModel;
private TProperty _testInputValue;
private TProperty _testResultValue;
private bool _updating;
private bool _canAddModifier;
private bool _isDataBindingEnabled;
public DataBindingViewModel(DataBindingRegistration<TLayerProperty, TProperty> registration,
IProfileEditorService profileEditorService,
IDataModelUIService dataModelUIService,
IDataBindingsVmFactory dataBindingsVmFactory)
{
Registration = registration;
_profileEditorService = profileEditorService;
_dataModelUIService = dataModelUIService;
_dataBindingsVmFactory = dataBindingsVmFactory;
if (Registration.Member != null)
@ -43,15 +37,10 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
else
DisplayName = Registration.LayerProperty.PropertyDescription.Name.ToUpper();
DataBindingModes = new BindableCollection<ValueDescription>(EnumUtilities.GetAllValuesAndDescriptions(typeof(DataBindingMode)));
DataBindingModes = new BindableCollection<ValueDescription>(EnumUtilities.GetAllValuesAndDescriptions(typeof(DataBindingModeType)));
EasingViewModels = new BindableCollection<TimelineEasingViewModel>();
ModifierViewModels = new BindableCollection<DataBindingModifierViewModel<TLayerProperty, TProperty>>();
DataBinding = Registration.DataBinding;
if (DataBinding != null)
DataBinding.ModifiersUpdated += DataBindingOnModifiersUpdated;
_isDataBindingEnabled = DataBinding != null;
Initialize();
}
@ -60,12 +49,21 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
public BindableCollection<ValueDescription> DataBindingModes { get; }
public BindableCollection<TimelineEasingViewModel> EasingViewModels { get; }
public BindableCollection<DataBindingModifierViewModel<TLayerProperty, TProperty>> ModifierViewModels { get; }
public DataBindingMode SelectedDataBindingMode
public DataBindingModeType SelectedDataBindingMode
{
get => _selectedDataBindingMode;
set => SetAndNotify(ref _selectedDataBindingMode, value);
set
{
if (!SetAndNotify(ref _selectedDataBindingMode, value)) return;
ApplyDataBindingMode();
}
}
public bool IsDataBindingEnabled
{
get => _isDataBindingEnabled;
set => SetAndNotify(ref _isDataBindingEnabled, value);
}
public TimelineEasingViewModel SelectedEasingViewModel
@ -98,39 +96,10 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
}
}
public DataModelDynamicViewModel TargetSelectionViewModel
{
get => _targetSelectionViewModel;
private set => SetAndNotify(ref _targetSelectionViewModel, value);
}
public bool IsDataBindingEnabled
{
get => _isDataBindingEnabled;
set
{
if (!SetAndNotify(ref _isDataBindingEnabled, value)) return;
if (value)
EnableDataBinding();
else
RemoveDataBinding();
}
}
public bool CanAddModifier
{
get => _canAddModifier;
private set => SetAndNotify(ref _canAddModifier, value);
}
public DataBinding<TLayerProperty, TProperty> DataBinding
{
get => _dataBinding;
set
{
if (!SetAndNotify(ref _dataBinding, value)) return;
UpdateModifierViewModels();
}
set => SetAndNotify(ref _dataBinding, value);
}
public TProperty TestInputValue
@ -145,79 +114,70 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
set => SetAndNotify(ref _testResultValue, value);
}
public void EnableDataBinding()
public void Dispose()
{
if (DataBinding != null)
return;
DataBinding = Registration.LayerProperty.EnableDataBinding(Registration);
DataBinding.ModifiersUpdated += DataBindingOnModifiersUpdated;
Update();
_profileEditorService.UpdateSelectedProfileElement();
}
public void RemoveDataBinding()
{
if (DataBinding == null)
return;
var toDisable = DataBinding;
DataBinding = null;
Registration.LayerProperty.DisableDataBinding(toDisable);
toDisable.ModifiersUpdated -= DataBindingOnModifiersUpdated;
Update();
_profileEditorService.UpdateSelectedProfileElement();
}
public void AddModifier()
{
if (DataBinding == null)
return;
var modifier = new DataBindingModifier<TLayerProperty, TProperty>(DataBinding, ProfileRightSideType.Dynamic);
DataBinding.AddModifier(modifier);
_profileEditorService.UpdateSelectedProfileElement();
_profileEditorService.ProfilePreviewUpdated -= ProfileEditorServiceOnProfilePreviewUpdated;
}
private void Initialize()
{
EasingViewModels.AddRange(Enum.GetValues(typeof(Easings.Functions)).Cast<Easings.Functions>().Select(v => new TimelineEasingViewModel(v, false)));
TargetSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_profileEditorService.GetCurrentModule());
TargetSelectionViewModel.PropertySelected += TargetSelectionViewModelOnPropertySelected;
_profileEditorService.ProfilePreviewUpdated += ProfileEditorServiceOnProfilePreviewUpdated;
CreateDataBindingModeModeViewModel();
Update();
}
private void CreateDataBindingModeModeViewModel()
{
if (DataBinding?.DataBindingMode == null)
{
ActiveItem = null;
return;
}
switch (DataBinding.DataBindingMode)
{
case DirectDataBinding<TLayerProperty, TProperty> directDataBinding:
ActiveItem = _dataBindingsVmFactory.DirectDataBindingModeViewModel(directDataBinding);
break;
case ConditionalDataBinding<TLayerProperty, TProperty> conditionalDataBinding:
ActiveItem = _dataBindingsVmFactory.ConditionalDataBindingModeViewModel(conditionalDataBinding);
break;
}
}
private void Update()
{
if (_updating)
return;
CanAddModifier = IsDataBindingEnabled && DataBinding.SourceDataModel != null;
if (DataBinding == null)
{
TargetSelectionViewModel.IsEnabled = false;
IsEasingTimeEnabled = false;
return;
}
_updating = true;
SelectedDataBindingMode = DataBinding.Mode;
IsDataBindingEnabled = ActiveItem != null;
EasingTime = (int) DataBinding.EasingTime.TotalMilliseconds;
SelectedEasingViewModel = EasingViewModels.First(vm => vm.EasingFunction == DataBinding.EasingFunction);
IsEasingTimeEnabled = EasingTime > 0;
switch (DataBinding.DataBindingMode)
{
case DirectDataBinding<TLayerProperty, TProperty> _:
SelectedDataBindingMode = DataBindingModeType.Direct;
break;
case ConditionalDataBinding<TLayerProperty, TProperty> _:
SelectedDataBindingMode = DataBindingModeType.Conditional;
break;
default:
SelectedDataBindingMode = DataBindingModeType.None;
break;
}
TargetSelectionViewModel.IsEnabled = true;
TargetSelectionViewModel.PopulateSelectedPropertyViewModel(DataBinding.SourceDataModel, DataBinding.SourcePropertyPath);
TargetSelectionViewModel.FilterTypes = new[] {DataBinding.GetTargetType()};
UpdateModifierViewModels();
ActiveItem?.Update();
_updating = false;
}
@ -229,7 +189,6 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
if (DataBinding != null)
{
DataBinding.Mode = SelectedDataBindingMode;
DataBinding.EasingTime = TimeSpan.FromMilliseconds(EasingTime);
DataBinding.EasingFunction = SelectedEasingViewModel?.EasingFunction ?? Easings.Functions.Linear;
}
@ -238,6 +197,27 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
Update();
}
private void ApplyDataBindingMode()
{
if (_updating)
return;
if (DataBinding != null && SelectedDataBindingMode == DataBindingModeType.None)
{
RemoveDataBinding();
CreateDataBindingModeModeViewModel();
return;
}
if (DataBinding == null && SelectedDataBindingMode != DataBindingModeType.None)
EnableDataBinding();
DataBinding.ChangeDataBindingMode(SelectedDataBindingMode);
CreateDataBindingModeModeViewModel();
_profileEditorService.UpdateSelectedProfileElement();
}
private void UpdateTestResult()
{
if (DataBinding == null)
@ -247,10 +227,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
return;
}
var currentValue = TargetSelectionViewModel.SelectedPropertyViewModel?.GetCurrentValue();
if (currentValue == null)
currentValue = default(TProperty);
var currentValue = ActiveItem?.GetTestValue() ?? default(TProperty);
TestInputValue = Registration.Converter.ConvertFromObject(currentValue);
if (DataBinding != null)
TestResultValue = DataBinding.GetValue(TestInputValue);
@ -258,49 +236,31 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
TestInputValue = default;
}
private void UpdateModifierViewModels()
private void EnableDataBinding()
{
foreach (var dataBindingModifierViewModel in ModifierViewModels)
dataBindingModifierViewModel.Dispose();
ModifierViewModels.Clear();
if (DataBinding != null)
return;
DataBinding = Registration.LayerProperty.EnableDataBinding(Registration);
_profileEditorService.UpdateSelectedProfileElement();
}
private void RemoveDataBinding()
{
if (DataBinding == null)
return;
foreach (var dataBindingModifier in DataBinding.Modifiers)
ModifierViewModels.Add(_dataBindingsVmFactory.DataBindingModifierViewModel(dataBindingModifier));
}
private void ProfileEditorServiceOnProfilePreviewUpdated(object sender, EventArgs e)
{
UpdateTestResult();
}
private void DataBindingOnModifiersUpdated(object sender, EventArgs e)
{
UpdateModifierViewModels();
}
private void TargetSelectionViewModelOnPropertySelected(object sender, DataModelInputDynamicEventArgs e)
{
DataBinding.UpdateSource(e.DataModelVisualizationViewModel.DataModel, e.DataModelVisualizationViewModel.PropertyPath);
var toDisable = DataBinding;
DataBinding = null;
Registration.LayerProperty.DisableDataBinding(toDisable);
Update();
_profileEditorService.UpdateSelectedProfileElement();
}
public void Dispose()
private void ProfileEditorServiceOnProfilePreviewUpdated(object sender, EventArgs e)
{
_profileEditorService.ProfilePreviewUpdated -= ProfileEditorServiceOnProfilePreviewUpdated;
TargetSelectionViewModel.PropertySelected -= TargetSelectionViewModelOnPropertySelected;
TargetSelectionViewModel.Dispose();
if (DataBinding != null)
DataBinding.ModifiersUpdated -= DataBindingOnModifiersUpdated;
foreach (var dataBindingModifierViewModel in ModifierViewModels)
dataBindingModifierViewModel.Dispose();
ModifierViewModels.Clear();
UpdateTestResult();
}
}

View File

@ -1,4 +1,4 @@
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DataBindingModifierView"
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDataBinding.DataBindingModifierView"
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"

View File

@ -1,17 +1,7 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDataBinding
{
/// <summary>
/// Interaction logic for DataBindingModifierView.xaml

View File

@ -7,7 +7,7 @@ using Artemis.UI.Shared.Input;
using Artemis.UI.Shared.Services;
using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDataBinding
{
public class DataBindingModifierViewModel<TLayerProperty, TProperty> : PropertyChangedBase, IDisposable
{
@ -63,13 +63,13 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
public void Delete()
{
Modifier.DataBinding.RemoveModifier(Modifier);
Modifier.DirectDataBinding.RemoveModifier(Modifier);
}
public void SwapType()
{
if (Modifier.ParameterType == ProfileRightSideType.Dynamic)
Modifier.UpdateParameter(Modifier.DataBinding.GetSourceType().GetDefault());
Modifier.UpdateParameter(Modifier.DirectDataBinding.GetSourceType().GetDefault());
else
Modifier.UpdateParameter(null, null);
@ -88,7 +88,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
private void Update()
{
var sourceType = Modifier.DataBinding.GetSourceType();
var sourceType = Modifier.DirectDataBinding.GetSourceType();
if (sourceType == null)
throw new ArtemisUIException("Cannot use a data binding modifier VM for a data binding without a source");

View File

@ -0,0 +1,61 @@
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDataBinding.DirectDataBindingModeView"
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:s="https://github.com/canton7/Stylet"
xmlns:dd="urn:gong-wpf-dragdrop"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Margin="5 10 0 0">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ContentControl Grid.Row="0"
s:View.Model="{Binding TargetSelectionViewModel}"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch"
IsTabStop="False" />
<Button Grid.Row="0"
Grid.Column="1"
Style="{StaticResource MaterialDesignFlatMidBgButton}"
HorizontalAlignment="Right"
FontSize="12"
Padding="6 4"
Height="22"
Command="{s:Action AddModifier}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Margin="0 -1 4 0" Kind="Plus" />
<TextBlock>
Add modifier
</TextBlock>
</StackPanel>
</Button>
<ListBox Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
ItemsSource="{Binding ModifierViewModels}"
materialDesign:RippleAssist.IsDisabled="True"
dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True"
dd:DragDrop.UseDefaultDragAdorner="True"
dd:DragDrop.DropHandler="{Binding}"
HorizontalContentAlignment="Stretch"
Margin="0 5 0 0">
<ListBox.ItemTemplate>
<DataTemplate>
<ContentControl s:View.Model="{Binding}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsTabStop="False" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>

View File

@ -0,0 +1,118 @@
using System;
using Artemis.Core;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Input;
using Artemis.UI.Shared.Services;
using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDataBinding
{
public class DirectDataBindingModeViewModel<TLayerProperty, TProperty> : Screen, IDataBindingModeViewModel
{
private readonly IDataBindingsVmFactory _dataBindingsVmFactory;
private readonly IDataModelUIService _dataModelUIService;
private readonly IProfileEditorService _profileEditorService;
private DataModelDynamicViewModel _targetSelectionViewModel;
public DirectDataBindingModeViewModel(DirectDataBinding<TLayerProperty, TProperty> directDataBinding,
IProfileEditorService profileEditorService,
IDataModelUIService dataModelUIService,
IDataBindingsVmFactory dataBindingsVmFactory)
{
_profileEditorService = profileEditorService;
_dataModelUIService = dataModelUIService;
_dataBindingsVmFactory = dataBindingsVmFactory;
DirectDataBinding = directDataBinding;
ModifierViewModels = new BindableCollection<DataBindingModifierViewModel<TLayerProperty, TProperty>>();
Initialize();
}
public DirectDataBinding<TLayerProperty, TProperty> DirectDataBinding { get; }
public BindableCollection<DataBindingModifierViewModel<TLayerProperty, TProperty>> ModifierViewModels { get; }
public DataModelDynamicViewModel TargetSelectionViewModel
{
get => _targetSelectionViewModel;
private set => SetAndNotify(ref _targetSelectionViewModel, value);
}
public void Update()
{
TargetSelectionViewModel.PopulateSelectedPropertyViewModel(DirectDataBinding.SourceDataModel, DirectDataBinding.SourcePropertyPath);
TargetSelectionViewModel.FilterTypes = new[] {DirectDataBinding.DataBinding.GetTargetType()};
UpdateModifierViewModels();
}
public object GetTestValue()
{
return TargetSelectionViewModel.SelectedPropertyViewModel?.GetCurrentValue();
}
private void Initialize()
{
DirectDataBinding.ModifiersUpdated += DirectDataBindingOnModifiersUpdated;
TargetSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_profileEditorService.GetCurrentModule());
TargetSelectionViewModel.PropertySelected += TargetSelectionViewModelOnPropertySelected;
Update();
}
#region Target
private void TargetSelectionViewModelOnPropertySelected(object sender, DataModelInputDynamicEventArgs e)
{
DirectDataBinding.UpdateSource(e.DataModelVisualizationViewModel.DataModel, e.DataModelVisualizationViewModel.PropertyPath);
Update();
_profileEditorService.UpdateSelectedProfileElement();
}
#endregion
#region Modifiers
public void AddModifier()
{
DirectDataBinding.AddModifier(ProfileRightSideType.Dynamic);
_profileEditorService.UpdateSelectedProfileElement();
}
private void UpdateModifierViewModels()
{
foreach (var dataBindingModifierViewModel in ModifierViewModels)
dataBindingModifierViewModel.Dispose();
ModifierViewModels.Clear();
foreach (var dataBindingModifier in DirectDataBinding.Modifiers)
ModifierViewModels.Add(_dataBindingsVmFactory.DataBindingModifierViewModel(dataBindingModifier));
}
private void DirectDataBindingOnModifiersUpdated(object sender, EventArgs e)
{
UpdateModifierViewModels();
}
#endregion
#region IDisposable
public void Dispose()
{
TargetSelectionViewModel.PropertySelected -= TargetSelectionViewModelOnPropertySelected;
TargetSelectionViewModel.Dispose();
DirectDataBinding.ModifiersUpdated -= DirectDataBindingOnModifiersUpdated;
foreach (var dataBindingModifierViewModel in ModifierViewModels)
dataBindingModifierViewModel.Dispose();
ModifierViewModels.Clear();
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
using System;
using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
{
public interface IDataBindingModeViewModel : IScreen, IDisposable
{
void Update();
object GetTestValue();
}
}

View File

@ -78,7 +78,7 @@
<!-- Left side -->
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="56" />
<RowDefinition Height="48" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
@ -173,7 +173,7 @@
</materialDesign:TransitionerSlide.BackwardWipe>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="56" />
<RowDefinition Height="48" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
@ -208,7 +208,7 @@
<!-- Timeline header body -->
<controls:PropertyTimelineHeader Margin="0 25 0 0"
<controls:PropertyTimelineHeader Margin="0 18 0 0"
Fill="{DynamicResource MaterialDesignBody}"
PixelsPerSecond="{Binding ProfileEditorService.PixelsPerSecond}"
HorizontalOffset="{Binding ContentHorizontalOffset, ElementName=TimelineHeaderScrollViewer}"
@ -250,7 +250,7 @@
<materialDesign:TransitionerSlide.BackwardWipe>
<materialDesign:CircleWipe />
</materialDesign:TransitionerSlide.BackwardWipe>
<Grid Background="{StaticResource MaterialDesignPaper}">
<Grid Background="{StaticResource MaterialDesignCardBackground}">
<ContentControl s:View.Model="{Binding DataBindingsViewModel}" />
</Grid>
</materialDesign:TransitionerSlide>

View File

@ -29,6 +29,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
set => SetAndNotify(ref _isVisible, value);
}
public bool IsExpanded => false;
public void Dispose()
{
TreePropertyViewModel?.Dispose();

View File

@ -129,9 +129,9 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline.Controls
var typeFace = new Typeface(FontFamily, new FontStyle(), new FontWeight(), new FontStretch());
var formattedText = new FormattedText(text, CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, typeFace, 9, Fill, null, VisualTreeHelper.GetDpi(this).PixelsPerDip);
if (x == 0 && OffsetFirstValue)
drawingContext.DrawText(formattedText, new Point(2, 2));
drawingContext.DrawText(formattedText, new Point(2, 5));
else
drawingContext.DrawText(formattedText, new Point(x - formattedText.Width / 2, 2));
drawingContext.DrawText(formattedText, new Point(x - formattedText.Width / 2, 5));
}
private void UpdateTimeScale()

View File

@ -1,4 +1,5 @@
using System;
using System.ComponentModel;
using System.Linq;
using Artemis.Core;
using Artemis.UI.Shared;
@ -58,7 +59,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree
}
#endregion
private void ApplyKeyframesEnabled(bool enable)
{
// If enabling keyframes for the first time, add a keyframe with the current value at the current position
@ -95,8 +96,9 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree
#endregion
}
public interface ITreePropertyViewModel : IScreen, IDisposable
public interface ITreePropertyViewModel : IScreen, INotifyPropertyChanged, IDisposable
{
bool HasPropertyInputViewModel { get; }
bool DataBindingsOpen { get; set; }
}
}

View File

@ -95,6 +95,11 @@
<Style TargetType="TreeViewItem" BasedOn="{StaticResource PropertyTreeStyle}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="Visibility" Value="{Binding IsVisible, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" />
<Style.Triggers>
<DataTrigger Binding="{Binding TreePropertyViewModel.DataBindingsOpen}" Value="True">
<Setter Property="Background" Value="{DynamicResource MaterialDesignPaper}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>