using System; using Artemis.Storage.Entities.Profile.DataBindings; namespace Artemis.Core { /// public class DataBinding : 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 dataBindingRegistration) { LayerProperty = dataBindingRegistration.LayerProperty; Entity = new DataBindingEntity(); ApplyRegistration(dataBindingRegistration); Save(); ApplyDataBindingMode(); } internal DataBinding(LayerProperty layerProperty, DataBindingEntity entity) { LayerProperty = layerProperty; Entity = entity; // Load will add children so be initialized before that Load(); ApplyDataBindingMode(); } /// /// Gets the data binding registration this data binding is based upon /// public DataBindingRegistration? Registration { get; private set; } /// /// Gets the layer property this data binding targets /// public LayerProperty LayerProperty { get; } /// /// Gets the converter used to apply this data binding to the /// public DataBindingConverter? Converter { get; private set; } /// /// Gets the data binding mode /// public IDataBindingMode? DataBindingMode { get; private set; } /// /// Gets or sets the easing time of the data binding /// public TimeSpan EasingTime { get; set; } /// /// Gets ors ets the easing function of the data binding /// public Easings.Functions EasingFunction { get; set; } internal DataBindingEntity Entity { get; } /// /// Gets the current value of the data binding /// /// The base value of the property the data binding is applied to /// 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(); } /// /// Returns the type of the target property of this data binding /// public Type? GetTargetType() { return Registration?.PropertyExpression.ReturnType; } private void ResetEasing(TProperty value) { _previousValue = GetInterpolatedValue(); _currentValue = value; _easingProgress = TimeSpan.Zero; } private void ApplyRegistration(DataBindingRegistration 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)); } /// /// Updates the smoothing progress of the data binding /// /// The timeline to apply during update 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); } /// 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; } /// 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 /// /// Releases the unmanaged resources used by the object and optionally releases the managed resources. /// /// /// to release both managed and unmanaged resources; /// to release only unmanaged resources. /// protected virtual void Dispose(bool disposing) { if (disposing) { _disposed = true; if (Registration != null) Registration.DataBinding = null; DataBindingMode?.Dispose(); } } /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion #region Mode management /// /// Changes the data binding mode of the data binding to the specified /// 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(this, directDataBindingEntity); break; case ConditionalDataBindingEntity conditionalDataBindingEntity: DataBindingMode = new ConditionalDataBinding(this, conditionalDataBindingEntity); break; } } #endregion #region Storage /// public void Load() { if (_disposed) throw new ObjectDisposedException("DataBinding"); // General DataBindingRegistration? registration = LayerProperty.GetDataBindingRegistration(Entity.TargetExpression); if (registration != null) ApplyRegistration(registration); EasingTime = Entity.EasingTime; EasingFunction = (Easings.Functions) Entity.EasingFunction; DataBindingMode?.Load(); } /// 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 } /// /// A mode that determines how the data binding is applied to the layer property /// public enum DataBindingModeType { /// /// Disables the data binding /// None, /// /// Replaces the layer property value with the data binding value /// Direct, /// /// Replaces the layer property value with the data binding value /// Conditional } }