1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00
Robert 099f56f4fe Profile editor - Added option to always update all data bindings
Profile editor - Moved data binding updating to main update loop
2020-12-18 22:44:17 +01:00

327 lines
11 KiB
C#

using System;
using Artemis.Storage.Entities.Profile.DataBindings;
namespace Artemis.Core
{
/// <inheritdoc />
public class DataBinding<TLayerProperty, TProperty> : IDataBinding
{
private TProperty _currentValue = default!;
private bool _disposed;
private TimeSpan _easingProgress;
private TProperty _lastAppliedValue = default!;
private TProperty _previousValue = default!;
private bool _reapplyValue;
internal DataBinding(DataBindingRegistration<TLayerProperty, TProperty> dataBindingRegistration)
{
LayerProperty = dataBindingRegistration.LayerProperty;
Entity = new DataBindingEntity();
ApplyRegistration(dataBindingRegistration);
Save();
ApplyDataBindingMode();
}
internal DataBinding(LayerProperty<TLayerProperty> layerProperty, DataBindingEntity entity)
{
LayerProperty = layerProperty;
Entity = entity;
// Load will add children so be initialized before that
Load();
ApplyDataBindingMode();
}
/// <summary>
/// Gets the data binding registration this data binding is based upon
/// </summary>
public DataBindingRegistration<TLayerProperty, TProperty>? Registration { get; private set; }
/// <summary>
/// Gets the layer property this data binding targets
/// </summary>
public LayerProperty<TLayerProperty> LayerProperty { get; }
/// <summary>
/// Gets the converter used to apply this data binding to the <see cref="LayerProperty" />
/// </summary>
public DataBindingConverter<TLayerProperty, TProperty>? Converter { get; private set; }
/// <summary>
/// Gets the data binding mode
/// </summary>
public IDataBindingMode<TLayerProperty, TProperty>? DataBindingMode { get; private set; }
/// <summary>
/// Gets or sets the easing time of the data binding
/// </summary>
public TimeSpan EasingTime { get; set; }
/// <summary>
/// Gets ors ets the easing function of the data binding
/// </summary>
public Easings.Functions EasingFunction { get; set; }
internal DataBindingEntity Entity { 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>
public TProperty GetValue(TProperty baseValue)
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
if (Converter == null || DataBindingMode == null)
return baseValue;
TProperty value = DataBindingMode.GetValue(baseValue);
// If no easing is to be applied simple return whatever the current value is
if (EasingTime == TimeSpan.Zero || !Converter.SupportsInterpolate)
return value;
// If the value changed, update the current and previous values used for easing
if (!Equals(value, _currentValue))
ResetEasing(value);
// Apply interpolation between the previous and current value
return GetInterpolatedValue();
}
/// <summary>
/// Returns the type of the target property of this data binding
/// </summary>
public Type? GetTargetType()
{
return Registration?.PropertyExpression.ReturnType;
}
private void ResetEasing(TProperty value)
{
_previousValue = GetInterpolatedValue();
_currentValue = value;
_easingProgress = TimeSpan.Zero;
}
private void ApplyRegistration(DataBindingRegistration<TLayerProperty, TProperty> dataBindingRegistration)
{
if (dataBindingRegistration == null)
throw new ArgumentNullException(nameof(dataBindingRegistration));
dataBindingRegistration.DataBinding = this;
Converter = dataBindingRegistration.Converter;
Registration = dataBindingRegistration;
if (GetTargetType()!.IsValueType)
{
if (_currentValue == null)
_currentValue = default!;
if (_previousValue == null)
_previousValue = default!;
}
Converter?.Initialize(this);
}
private TProperty GetInterpolatedValue()
{
if (_easingProgress == EasingTime || Converter == null || !Converter.SupportsInterpolate)
return _currentValue;
double easingAmount = _easingProgress.TotalSeconds / EasingTime.TotalSeconds;
return Converter.Interpolate(_previousValue, _currentValue, Easings.Interpolate(easingAmount, EasingFunction));
}
/// <summary>
/// Updates the smoothing progress of the data binding
/// </summary>
/// <param name="timeline">The timeline to apply during update</param>
public void Update(Timeline timeline)
{
// Don't update data bindings if there is no delta, otherwise this creates an inconsistency between
// data bindings with easing and data bindings without easing (the ones with easing will seemingly not update)
if (timeline.Delta == TimeSpan.Zero)
return;
UpdateWithDelta(timeline.Delta);
}
/// <inheritdoc />
public void UpdateWithDelta(TimeSpan delta)
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
// Data bindings cannot go back in time like brushes
if (delta < TimeSpan.Zero)
delta = TimeSpan.Zero;
_easingProgress = _easingProgress.Add(delta);
if (_easingProgress > EasingTime)
_easingProgress = EasingTime;
// Tell Apply() to apply a new value next call
_reapplyValue = false;
}
/// <inheritdoc />
public void Apply()
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
if (Converter == null)
return;
// If Update() has not been called, reapply the previous value
if (_reapplyValue)
{
Converter.ApplyValue(_lastAppliedValue);
return;
}
TProperty converterValue = Converter.GetValue();
TProperty value = GetValue(converterValue);
Converter.ApplyValue(value);
_lastAppliedValue = value;
_reapplyValue = true;
}
#region IDisposable
/// <summary>
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_disposed = true;
if (Registration != null)
Registration.DataBinding = null;
DataBindingMode?.Dispose();
}
}
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
#region Mode management
/// <summary>
/// Changes the data binding mode of the data binding to the specified <paramref name="dataBindingMode" />
/// </summary>
public void ChangeDataBindingMode(DataBindingModeType dataBindingMode)
{
switch (dataBindingMode)
{
case DataBindingModeType.Direct:
Entity.DataBindingMode = new DirectDataBindingEntity();
break;
case DataBindingModeType.Conditional:
Entity.DataBindingMode = new ConditionalDataBindingEntity();
break;
default:
Entity.DataBindingMode = null;
break;
}
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 />
public void Load()
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
// General
DataBindingRegistration<TLayerProperty, TProperty>? registration = LayerProperty.GetDataBindingRegistration<TProperty>(Entity.TargetExpression);
if (registration != null)
ApplyRegistration(registration);
EasingTime = Entity.EasingTime;
EasingFunction = (Easings.Functions) Entity.EasingFunction;
DataBindingMode?.Load();
}
/// <inheritdoc />
public void Save()
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
if (!LayerProperty.Entity.DataBindingEntities.Contains(Entity))
LayerProperty.Entity.DataBindingEntities.Add(Entity);
// Don't save an invalid state
if (Registration != null)
Entity.TargetExpression = Registration.PropertyExpression.ToString();
Entity.EasingTime = EasingTime;
Entity.EasingFunction = (int) EasingFunction;
DataBindingMode?.Save();
}
#endregion
}
/// <summary>
/// A mode that determines how the data binding is applied to the layer property
/// </summary>
public enum DataBindingModeType
{
/// <summary>
/// Disables the data binding
/// </summary>
None,
/// <summary>
/// Replaces the layer property value with the data binding value
/// </summary>
Direct,
/// <summary>
/// Replaces the layer property value with the data binding value
/// </summary>
Conditional
}
}