Databinding modifiers - Redesigned API to leverage generics

Databinding modifiers - Updated default modifiers to use new API
This commit is contained in:
SpoinkyNL committed 2020-10-20 19:00:41 +02:00
1 parent 2bb663db15
commit fd9f3afef5
50 files changed
+487 -407

No files matched your search

@@ -1,79 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Artemis.Core
{
/// <summary>
/// A modifier that changes the source value of a data binding in some way
/// </summary>
public abstract class DataBindingModifierType
{
/// <summary>
/// Gets the plugin info this data binding modifier belongs to
/// <para>Note: Not set until after registering</para>
/// </summary>
public PluginInfo PluginInfo { get; internal set; }
/// <summary>
/// Gets the types this modifier supports
/// </summary>
public abstract IReadOnlyCollection<Type> CompatibleTypes { get; }
/// <summary>
/// Gets the supported parameter type
/// <para>If <c>null</c>, the parameter type will match the source property</para>
/// </summary>
public virtual Type ParameterType => null;
/// <summary>
/// Gets or sets whether this modifier supports a parameter, defaults to <c>true</c>
/// </summary>
public virtual bool SupportsParameter => true;
/// <summary>
/// Gets the name of this modifier
/// </summary>
public abstract string Name { get; }
/// <summary>
/// Gets or sets the icon of this modifier
/// </summary>
public abstract string Icon { get; }
/// <summary>
/// Gets the description of this modifier
/// </summary>
public virtual string Description => null;
/// <summary>
/// Gets the category of this modifier
/// </summary>
public virtual string Category => null;
/// <summary>
/// Returns whether the given type is supported by the modifier
/// </summary>
public bool SupportsType(Type type)
{
if (type == null)
return true;
return CompatibleTypes.Any(t => t.IsCastableFrom(type));
}
/// <summary>
/// Called whenever the modifier must apply to a specific value, must be a value of a type contained in
/// <see cref="CompatibleTypes" />
/// </summary>
/// <param name="currentValue">
/// The current value before modification, is always of a type contained in
/// <see cref="CompatibleTypes" />
/// </param>
/// <param name="parameterValue">
/// The parameter to use for the modification, is always of a type contained in
/// <see cref="CompatibleTypes" />
/// </param>
/// <returns>The modified value, must be a value of a type contained in <see cref="CompatibleTypes" /></returns>
public abstract object Apply(object currentValue, object parameterValue);
}
}
@@ -0,0 +1,82 @@
using System;
namespace Artemis.Core
{
/// <summary>
/// A modifier that changes the source value of a data binding in some way
/// <para>
/// To implement your own condition operator, inherit <see cref="DataBindingModifierType{TValue, TParameter}" /> or
/// <see cref="DataBindingModifierType{TValue}" />
/// </para>
/// </summary>
public abstract class BaseDataBindingModifierType
{
/// <summary>
/// Gets the plugin info this data binding modifier belongs to
/// <para>Note: Not set until after registering</para>
/// </summary>
public PluginInfo PluginInfo { get; internal set; }
/// <summary>
/// Gets the value type of this modifier type
/// </summary>
public abstract Type ValueType { get; }
/// <summary>
/// Gets the parameter type of this modifier type. May be null if the modifier type does not support a parameter
/// </summary>
public abstract Type? ParameterType { get; }
/// <summary>
/// Gets the name of this modifier
/// </summary>
public abstract string Name { get; }
/// <summary>
/// Gets or sets the icon of this modifier
/// </summary>
public abstract string Icon { get; }
/// <summary>
/// Gets the description of this modifier
/// </summary>
public virtual string Description => null;
/// <summary>
/// Gets the category of this modifier
/// </summary>
public virtual string Category => null;
/// <summary>
/// Returns whether the given type is supported by the modifier
/// </summary>
/// <param name="type">The type to check for, must be either the same or be castable to the target type</param>
/// <param name="part">Which part of the modifier to check, the value or the parameter</param>
public bool SupportsType(Type type, ModifierTypePart part)
{
if (type == null)
return true;
if (part == ModifierTypePart.Value)
return ValueType.IsCastableFrom(type);
return ParameterType != null && ParameterType.IsCastableFrom(type);
}
/// <summary>
/// Applies the modifier to the provided current value
/// <para>
/// This leaves the caller responsible for the types matching <see cref="ValueType" /> and
/// <see cref="ParameterType" />
/// </para>
/// </summary>
/// <param name="currentValue">The current value before modification, type should match <see cref="ValueType" /></param>
/// <param name="parameterValue">The parameter to use for the modification, type should match <see cref="ParameterType" /></param>
/// <returns>The modified value, with a type of <see cref="ValueType" /></returns>
internal abstract object? InternalApply(object? currentValue, object? parameterValue);
}
public enum ModifierTypePart
{
Value,
Parameter
}
}
@@ -30,7 +30,7 @@ namespace Artemis.Core
/// <summary>
/// Gets the type of modifier that is being applied
/// </summary>
public DataBindingModifierType ModifierType { get; private set; }
public BaseDataBindingModifierType? ModifierType { get; private set; }
/// <summary>
/// Gets the direct data binding this modifier is applied to
@@ -40,7 +40,7 @@ namespace Artemis.Core
/// <summary>
/// Gets the type of the parameter, can either be dynamic (based on a data model value) or static
/// </summary>
public ProfileRightSideType ParameterType { get; private set; }
public ProfileRightSideType ParameterType { get; set; }
/// <summary>
/// Gets or sets the position at which the modifier appears on the data binding
@@ -56,7 +56,7 @@ namespace Artemis.Core
/// Gets the parameter static value, only used it <see cref="ParameterType" /> is
/// <see cref="ProfileRightSideType.Static" />
/// </summary>
public object ParameterStaticValue { get; private set; }
public object? ParameterStaticValue { get; private set; }
internal DataBindingModifierEntity Entity { get; set; }
@@ -65,7 +65,7 @@ namespace Artemis.Core
/// </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)
public object? Apply(object? currentValue)
{
if (_disposed)
throw new ObjectDisposedException("DataBindingModifier");
@@ -73,17 +73,17 @@ namespace Artemis.Core
if (ModifierType == null)
return currentValue;
if (!ModifierType.SupportsParameter)
return ModifierType.Apply(currentValue, null);
if (ModifierType.ParameterType == null)
return ModifierType.InternalApply(currentValue, null);
if (ParameterType == ProfileRightSideType.Dynamic && ParameterPath != null && ParameterPath.IsValid)
{
object? value = ParameterPath.GetValue();
return ModifierType.Apply(currentValue, value);
return ModifierType.InternalApply(currentValue, value);
}
if (ParameterType == ProfileRightSideType.Static)
return ModifierType.Apply(currentValue, ParameterStaticValue);
return ModifierType.InternalApply(currentValue, ParameterStaticValue);
return currentValue;
}
@@ -92,12 +92,11 @@ namespace Artemis.Core
/// Updates the modifier type of the modifier and re-compiles the expression
/// </summary>
/// <param name="modifierType"></param>
public void UpdateModifierType(DataBindingModifierType modifierType)
public void UpdateModifierType(BaseDataBindingModifierType? modifierType)
{
if (_disposed)
throw new ObjectDisposedException("DataBindingModifier");
// Calling CreateExpression will clear compiled expressions
if (modifierType == null)
{
ModifierType = null;
@@ -105,11 +104,36 @@ namespace Artemis.Core
}
Type targetType = DirectDataBinding.DataBinding.GetTargetType();
if (!modifierType.SupportsType(targetType))
if (!modifierType.SupportsType(targetType, ModifierTypePart.Value))
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;
ValidateParameter();
}
private void ValidateParameter()
{
if (ModifierType == null)
return;
if (ParameterType == ProfileRightSideType.Dynamic)
{
if (ParameterPath == null || !ParameterPath.IsValid)
return;
Type parameterType = ParameterPath.GetPropertyType()!;
if (!ModifierType.SupportsType(parameterType, ModifierTypePart.Parameter))
UpdateParameterDynamic(null);
}
else
{
if (ParameterStaticValue == null)
return;
if (!ModifierType.SupportsType(ParameterStaticValue.GetType(), ModifierTypePart.Parameter))
UpdateParameterStatic(null);
}
}
/// <summary>
@@ -165,7 +189,7 @@ namespace Artemis.Core
// Modifier type
if (Entity.ModifierTypePluginGuid != null && ModifierType == null)
{
DataBindingModifierType modifierType = DataBindingModifierTypeStore.Get(Entity.ModifierTypePluginGuid.Value, Entity.ModifierType)?.DataBindingModifierType;
BaseDataBindingModifierType modifierType = DataBindingModifierTypeStore.Get(Entity.ModifierTypePluginGuid.Value, Entity.ModifierType)?.DataBindingModifierType;
if (modifierType != null)
UpdateModifierType(modifierType);
}
@@ -261,7 +285,7 @@ namespace Artemis.Core
if (ModifierType != null)
return;
DataBindingModifierType modifierType = e.TypeRegistration.DataBindingModifierType;
BaseDataBindingModifierType modifierType = e.TypeRegistration.DataBindingModifierType;
if (modifierType.PluginInfo.Guid == Entity.ModifierTypePluginGuid && modifierType.GetType().Name == Entity.ModifierType)
UpdateModifierType(modifierType);
}
@@ -0,0 +1,81 @@
using System;
namespace Artemis.Core
{
/// <summary>
/// A modifier that changes the source value of a data binding in some way using a parameter
/// </summary>
public abstract class DataBindingModifierType<TValue, TParameter> : BaseDataBindingModifierType
{
/// <inheritdoc />
public override Type ValueType => typeof(TValue);
/// <inheritdoc />
public override Type ParameterType => typeof(TParameter);
/// <summary>
/// Called whenever the modifier must apply to a specific value
/// </summary>
/// <param name="currentValue">
/// The current value before modification
/// </param>
/// <param name="parameterValue">
/// The parameter to use for the modification
/// </param>
/// <returns>The modified value></returns>
public abstract TValue Apply(TValue currentValue, TParameter parameterValue);
/// <inheritdoc />
internal override object? InternalApply(object? currentValue, object? parameterValue)
{
// TODO: Can we avoid boxing/unboxing?
TValue current;
if (currentValue != null)
current = (TValue) Convert.ChangeType(currentValue, typeof(TValue));
else
current = default;
TParameter parameter;
if (parameterValue != null)
parameter = (TParameter) Convert.ChangeType(parameterValue, typeof(TParameter));
else
parameter = default;
return Apply(current!, parameter!);
}
}
/// <summary>
/// A modifier that changes the source value of a data binding in some way
/// </summary>
public abstract class DataBindingModifierType<TValue> : BaseDataBindingModifierType
{
/// <inheritdoc />
public override Type ValueType => typeof(TValue);
/// <inheritdoc />
public override Type? ParameterType => null;
/// <summary>
/// Called whenever the modifier must apply to a specific value
/// </summary>
/// <param name="currentValue">
/// The current value before modification
/// </param>
/// <returns>The modified value</returns>
public abstract TValue Apply(TValue currentValue);
/// <inheritdoc />
internal override object? InternalApply(object? currentValue, object? parameterValue)
{
// TODO: Can we avoid boxing/unboxing?
TValue current;
if (currentValue != null)
current = (TValue) Convert.ChangeType(currentValue, typeof(TValue));
else
current = default;
return Apply(current!);
}
}
}