Core - Layer refactor WIP

This commit is contained in:
Robert committed 2020-09-08 17:32:40 +02:00
1 parent 4dcefa6667
commit 5096c7b30d
33 files changed
+941 -895

No files matched your search

@@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Artemis.Core.Services;
using Artemis.Storage.Entities.Profile;
using Newtonsoft.Json;
@@ -17,68 +17,114 @@ namespace Artemis.Core
/// </para>
/// </summary>
/// <typeparam name="T">The type of property encapsulated in this layer property</typeparam>
public abstract class LayerProperty<T> : BaseLayerProperty
public abstract class LayerProperty<T> : ILayerProperty
{
private bool _isInitialized;
private List<LayerPropertyKeyframe<T>> _keyframes;
/// <summary>
/// Creates a new instance of the <see cref="LayerProperty{T}" /> class
/// </summary>
protected LayerProperty()
{
_keyframes = new List<LayerPropertyKeyframe<T>>();
}
/// <summary>
/// Gets or sets the base value of this layer property without any keyframes applied
/// Gets the description attribute applied to this property
/// </summary>
public new T BaseValue
public PropertyDescriptionAttribute PropertyDescription { get; internal set; }
/// <summary>
/// Updates the property, applying keyframes and data bindings to the current value
/// </summary>
public void Update(double deltaTime)
{
get => base.BaseValue != null ? (T) base.BaseValue : default;
CurrentValue = BaseValue;
UpdateKeyframes();
UpdateDataBindings(deltaTime);
OnUpdated();
}
/// <summary>
/// Returns the type of the property
/// </summary>
public Type GetPropertyType()
{
return typeof(T);
}
#region Hierarchy
private bool _isHidden;
/// <summary>
/// Gets or sets whether the property is hidden in the UI
/// </summary>
public bool IsHidden
{
get => _isHidden;
set
{
if (Equals(base.BaseValue, value))
_isHidden = value;
OnVisibilityChanged();
}
}
/// <summary>
/// Gets the profile element (such as layer or folder) this property is applied to
/// </summary>
public RenderProfileElement ProfileElement { get; internal set; }
/// <summary>
/// The parent group of this layer property, set after construction
/// </summary>
public LayerPropertyGroup LayerPropertyGroup { get; internal set; }
#endregion
#region Value management
private T _baseValue;
/// <summary>
/// Called every update (if keyframes are both supported and enabled) to determine the new <see cref="CurrentValue" />
/// based on the provided progress
/// </summary>
/// <param name="keyframeProgress">The linear current keyframe progress</param>
/// <param name="keyframeProgressEased">The current keyframe progress, eased with the current easing function</param>
protected virtual void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
{
throw new NotImplementedException();
}
/// <summary>
/// Gets or sets the base value of this layer property without any keyframes applied
/// </summary>
public T BaseValue
{
get => _baseValue;
set
{
if (Equals(_baseValue, value))
return;
base.BaseValue = value;
Update();
_baseValue = value;
Update(0);
OnBaseValueChanged();
LayerPropertyGroup.OnLayerPropertyBaseValueChanged(new LayerPropertyEventArgs(this));
}
}
/// <summary>
/// Gets the current value of this property as it is affected by it's keyframes, updated once every frame
/// </summary>
public new T CurrentValue
{
get => base.CurrentValue != null ? (T) base.CurrentValue : default;
set => base.CurrentValue = value;
}
public T CurrentValue { get; set; }
/// <summary>
/// Gets or sets the default value of this layer property. If set, this value is automatically applied if the property
/// has no value in storage
/// </summary>
public new T DefaultValue
{
get => base.DefaultValue != null ? (T) base.DefaultValue : default;
set => base.DefaultValue = value;
}
/// <summary>
/// Gets a read-only list of all the keyframes on this layer property
/// </summary>
public IReadOnlyList<LayerPropertyKeyframe<T>> Keyframes => _keyframes.AsReadOnly();
/// <summary>
/// Gets the current keyframe in the timeline according to the current progress
/// </summary>
public LayerPropertyKeyframe<T> CurrentKeyframe { get; protected set; }
/// <summary>
/// Gets the next keyframe in the timeline according to the current progress
/// </summary>
public LayerPropertyKeyframe<T> NextKeyframe { get; protected set; }
public override IReadOnlyList<BaseLayerPropertyKeyframe> BaseKeyframes => _keyframes.Cast<BaseLayerPropertyKeyframe>().ToList().AsReadOnly();
public T DefaultValue { get; set; }
/// <summary>
/// Sets the current value, using either keyframes if enabled or the base value.
@@ -105,9 +151,61 @@ namespace Artemis.Core
// Force an update so that the base value is applied to the current value and
// keyframes/data bindings are applied using the new base value
Update();
Update(0);
}
/// <summary>
/// Overrides the property value with the default value
/// </summary>
public void ApplyDefaultValue()
{
BaseValue = DefaultValue;
CurrentValue = DefaultValue;
}
#endregion
#region Keyframes
private bool _keyframesEnabled;
private List<LayerPropertyKeyframe<T>> _keyframes;
/// <summary>
/// Gets whether keyframes are supported on this type of property
/// </summary>
public bool KeyframesSupported { get; protected internal set; } = true;
/// <summary>
/// Gets or sets whether keyframes are enabled on this property, has no effect if <see cref="KeyframesSupported" /> is
/// False
/// </summary>
public bool KeyframesEnabled
{
get => _keyframesEnabled;
set
{
if (_keyframesEnabled == value) return;
_keyframesEnabled = value;
OnKeyframesToggled();
}
}
/// <summary>
/// Gets a read-only list of all the keyframes on this layer property
/// </summary>
public IReadOnlyList<LayerPropertyKeyframe<T>> Keyframes => _keyframes.AsReadOnly();
/// <summary>
/// Gets the current keyframe in the timeline according to the current progress
/// </summary>
public LayerPropertyKeyframe<T> CurrentKeyframe { get; protected set; }
/// <summary>
/// Gets the next keyframe in the timeline according to the current progress
/// </summary>
public LayerPropertyKeyframe<T> NextKeyframe { get; protected set; }
/// <summary>
/// Adds a keyframe to the layer property
/// </summary>
@@ -118,10 +216,9 @@ namespace Artemis.Core
return;
keyframe.LayerProperty?.RemoveKeyframe(keyframe);
keyframe.LayerProperty = this;
keyframe.BaseLayerProperty = this;
_keyframes.Add(keyframe);
SortKeyframes();
OnKeyframeAdded();
}
@@ -154,7 +251,6 @@ namespace Artemis.Core
_keyframes.Remove(keyframe);
keyframe.LayerProperty = null;
keyframe.BaseLayerProperty = null;
SortKeyframes();
OnKeyframeRemoved();
}
@@ -169,43 +265,6 @@ namespace Artemis.Core
RemoveKeyframe(layerPropertyKeyframe);
}
/// <inheritdoc />
public override void ApplyDefaultValue()
{
BaseValue = DefaultValue;
CurrentValue = DefaultValue;
}
/// <inheritdoc />
public override Type GetPropertyType()
{
return typeof(T);
}
/// <summary>
/// Called every update (if keyframes are both supported and enabled) to determine the new <see cref="CurrentValue" />
/// based on the provided progress
/// </summary>
/// <param name="keyframeProgress">The linear current keyframe progress</param>
/// <param name="keyframeProgressEased">The current keyframe progress, eased with the current easing function</param>
protected virtual void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
{
throw new NotImplementedException();
}
/// <summary>
/// Updates the property, applying keyframes and data bindings to the current value
/// </summary>
internal void Update(double deltaTime = 0)
{
CurrentValue = BaseValue;
UpdateKeyframes();
UpdateDataBindings(deltaTime);
OnUpdated();
}
/// <summary>
/// Sorts the keyframes in ascending order by position
/// </summary>
@@ -214,77 +273,6 @@ namespace Artemis.Core
_keyframes = _keyframes.OrderBy(k => k.Position).ToList();
}
internal override void ApplyToLayerProperty(PropertyEntity entity, LayerPropertyGroup layerPropertyGroup, bool fromStorage)
{
// Doubt this will happen but let's make sure
if (_isInitialized)
throw new ArtemisCoreException("Layer property already initialized, wut");
PropertyEntity = entity;
LayerPropertyGroup = layerPropertyGroup;
LayerPropertyGroup.PropertyGroupUpdating += (sender, args) => Update(args.DeltaTime);
try
{
if (entity.Value != null)
BaseValue = JsonConvert.DeserializeObject<T>(entity.Value);
IsLoadedFromStorage = fromStorage;
CurrentValue = BaseValue;
KeyframesEnabled = entity.KeyframesEnabled;
_keyframes.Clear();
_keyframes.AddRange(entity.KeyframeEntities.Select(k => new LayerPropertyKeyframe<T>(
JsonConvert.DeserializeObject<T>(k.Value),
k.Position,
(Easings.Functions) k.EasingFunction,
this
)));
_dataBindings.Clear();
foreach (var entityDataBindingEntity in entity.DataBindingEntities)
{
var dataBinding = new DataBinding(this, entityDataBindingEntity);
_dataBindings.Add(dataBinding);
}
}
catch (JsonException e)
{
// TODO: Properly log the JSON exception
Debug.WriteLine($"JSON exception while deserializing: {e}");
IsLoadedFromStorage = false;
}
finally
{
SortKeyframes();
_isInitialized = true;
}
}
internal override void ApplyToEntity()
{
if (!_isInitialized)
throw new ArtemisCoreException("Layer property is not yet initialized");
PropertyEntity.Value = JsonConvert.SerializeObject(BaseValue);
PropertyEntity.KeyframesEnabled = KeyframesEnabled;
PropertyEntity.KeyframeEntities.Clear();
PropertyEntity.KeyframeEntities.AddRange(Keyframes.Select(k => new KeyframeEntity
{
Value = JsonConvert.SerializeObject(k.Value),
Position = k.Position,
EasingFunction = (int) k.EasingFunction
}));
PropertyEntity.DataBindingEntities.Clear();
foreach (var dataBinding in DataBindings)
{
dataBinding.ApplyToEntity();
PropertyEntity.DataBindingEntities.Add(dataBinding.Entity);
}
}
private void UpdateKeyframes()
{
if (!KeyframesSupported || !KeyframesEnabled)
@@ -311,17 +299,32 @@ namespace Artemis.Core
}
}
#endregion
#region Data bindings
public void RegisterDataBindingProperty<TProperty>(Expression<Func<T, TProperty>> propertyLambda, DataBindingConverter converter)
internal readonly List<IDataBindingRegistration> _dataBindingRegistrations = new List<IDataBindingRegistration>();
internal readonly List<IDataBinding> _dataBindings = new List<IDataBinding>();
/// <summary>
/// Gets whether data bindings are supported on this type of property
/// </summary>
public bool DataBindingsSupported { get; protected internal set; } = true;
public DataBindingRegistration<T, TProperty> GetDataBindingRegistration<TProperty>(string propertyName)
{
var match = _dataBindingRegistrations.FirstOrDefault(r => r is DataBindingRegistration<T, TProperty> registration &&
registration.Property.Name == propertyName);
return (DataBindingRegistration<T, TProperty>) match;
}
public void RegisterDataBindingProperty<TProperty>(Expression<Func<T, TProperty>> propertyLambda, DataBindingConverter<T, TProperty> converter)
{
// If the lambda references to itself, use the property info of public new T CurrentValue
PropertyInfo propertyInfo;
string path = null;
if (propertyLambda.Parameters[0] == propertyLambda.Body)
{
propertyInfo = GetType().GetProperties().FirstOrDefault(p => p.Name == nameof(CurrentValue) && p.PropertyType == typeof(T));
}
else
{
propertyInfo = ReflectionUtilities.GetPropertyInfo(CurrentValue, propertyLambda);
@@ -341,18 +344,211 @@ namespace Artemis.Core
"because the provided converter does not support the property's type");
}
_dataBindingRegistrations.Add(new DataBindingRegistration(this, propertyInfo, converter, path));
_dataBindingRegistrations.Add(new DataBindingRegistration<T, TProperty>(this, converter, propertyInfo, path));
}
/// <summary>
/// Enables a data binding for the provided <paramref name="dataBindingRegistration" />
/// </summary>
/// <returns>The newly created data binding</returns>
public DataBinding<T, TProperty> EnableDataBinding<TProperty>(DataBindingRegistration<T, TProperty> dataBindingRegistration)
{
if (dataBindingRegistration.LayerProperty != this)
throw new ArtemisCoreException("Cannot enable a data binding using a data binding registration of a different layer property");
var dataBinding = new DataBinding<T, TProperty>(dataBindingRegistration);
_dataBindings.Add(dataBinding);
return dataBinding;
}
/// <summary>
/// Disables the provided data binding
/// </summary>
/// <param name="dataBinding">The data binding to remove</param>
public void DisableDataBinding<TProperty>(DataBinding<T, TProperty> dataBinding)
{
_dataBindings.Remove(dataBinding);
}
private void UpdateDataBindings(double deltaTime)
{
foreach (var dataBinding in DataBindings)
foreach (var dataBinding in _dataBindings)
{
dataBinding.Update(deltaTime);
dataBinding.ApplyToProperty();
dataBinding.Apply();
}
}
internal void InitializeDataBindings(IDataModelService dataModelService, IDataBindingService dataBindingService)
{
foreach (var dataBinding in _dataBindings)
dataBinding.Initialize(dataModelService, dataBindingService);
}
#endregion
#region Storage
private bool _isInitialized;
/// <summary>
/// Indicates whether the BaseValue was loaded from storage, useful to check whether a default value must be applied
/// </summary>
public bool IsLoadedFromStorage { get; internal set; }
internal PropertyEntity Entity { get; set; }
/// <inheritdoc />
public void Initialize(RenderProfileElement profileElement, LayerPropertyGroup group, PropertyEntity entity, bool fromStorage, PropertyDescriptionAttribute description)
{
_isInitialized = true;
ProfileElement = profileElement ?? throw new ArgumentNullException(nameof(profileElement));
LayerPropertyGroup = group ?? throw new ArgumentNullException(nameof(group));
Entity = entity ?? throw new ArgumentNullException(nameof(entity));
PropertyDescription = description ?? throw new ArgumentNullException(nameof(description));
IsLoadedFromStorage = fromStorage;
LayerPropertyGroup.PropertyGroupUpdating += (sender, args) => Update(args.DeltaTime);
}
/// <inheritdoc />
public void Load()
{
if (!_isInitialized)
throw new ArtemisCoreException("Layer property is not yet initialized");
if (!IsLoadedFromStorage)
ApplyDefaultValue();
else
{
try
{
if (Entity.Value != null)
BaseValue = JsonConvert.DeserializeObject<T>(Entity.Value);
}
catch (JsonException e)
{
// ignored for now
}
}
CurrentValue = BaseValue;
KeyframesEnabled = Entity.KeyframesEnabled;
_keyframes.Clear();
try
{
_keyframes.AddRange(Entity.KeyframeEntities.Select(k => new LayerPropertyKeyframe<T>(
JsonConvert.DeserializeObject<T>(k.Value),
k.Position,
(Easings.Functions) k.EasingFunction,
this
)));
}
catch (JsonException e)
{
// ignored for now
}
_dataBindings.Clear();
foreach (var dataBindingRegistration in _dataBindingRegistrations)
{
var dataBinding = dataBindingRegistration.CreateDataBinding();
if (dataBinding != null)
_dataBindings.Add(dataBinding);
}
}
/// <summary>
/// Saves the property to the underlying property entity that was configured when calling
/// <see cref="ApplyToLayerProperty" />
/// </summary>
public void Save()
{
if (!_isInitialized)
throw new ArtemisCoreException("Layer property is not yet initialized");
Entity.Value = JsonConvert.SerializeObject(BaseValue);
Entity.KeyframesEnabled = KeyframesEnabled;
Entity.KeyframeEntities.Clear();
Entity.KeyframeEntities.AddRange(Keyframes.Select(k => new KeyframeEntity
{
Value = JsonConvert.SerializeObject(k.Value),
Position = k.Position,
EasingFunction = (int) k.EasingFunction
}));
Entity.DataBindingEntities.Clear();
foreach (var dataBinding in _dataBindings)
dataBinding.Save();
}
#endregion
#region Events
/// <summary>
/// Occurs once every frame when the layer property is updated
/// </summary>
public event EventHandler<LayerPropertyEventArgs<T>> Updated;
/// <summary>
/// Occurs when the base value of the layer property was updated
/// </summary>
public event EventHandler<LayerPropertyEventArgs<T>> BaseValueChanged;
/// <summary>
/// Occurs when the <see cref="IsHidden" /> value of the layer property was updated
/// </summary>
public event EventHandler<LayerPropertyEventArgs<T>> VisibilityChanged;
/// <summary>
/// Occurs when keyframes are enabled/disabled
/// </summary>
public event EventHandler<LayerPropertyEventArgs<T>> KeyframesToggled;
/// <summary>
/// Occurs when a new keyframe was added to the layer property
/// </summary>
public event EventHandler<LayerPropertyEventArgs<T>> KeyframeAdded;
/// <summary>
/// Occurs when a keyframe was removed from the layer property
/// </summary>
public event EventHandler<LayerPropertyEventArgs<T>> KeyframeRemoved;
protected virtual void OnUpdated()
{
Updated?.Invoke(this, new LayerPropertyEventArgs<T>(this));
}
protected virtual void OnBaseValueChanged()
{
BaseValueChanged?.Invoke(this, new LayerPropertyEventArgs<T>(this));
}
protected virtual void OnVisibilityChanged()
{
VisibilityChanged?.Invoke(this, new LayerPropertyEventArgs<T>(this));
}
protected virtual void OnKeyframesToggled()
{
KeyframesToggled?.Invoke(this, new LayerPropertyEventArgs<T>(this));
}
protected virtual void OnKeyframeAdded()
{
KeyframeAdded?.Invoke(this, new LayerPropertyEventArgs<T>(this));
}
protected virtual void OnKeyframeRemoved()
{
KeyframeRemoved?.Invoke(this, new LayerPropertyEventArgs<T>(this));
}
#endregion
}
}