Data bindings - Rewrote data binding system to fully leverage nodes

This commit is contained in:
Robert committed 2021-08-22 18:17:05 +02:00
1 parent 7933bf7ee9
commit 0bb93f2ebd
63 files changed
+654 -1533

No files matched your search

@@ -1,25 +1,24 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Artemis.Storage.Entities.Profile.DataBindings;
namespace Artemis.Core
{
/// <inheritdoc />
public class DataBinding<TLayerProperty, TProperty> : IDataBinding
public class DataBinding<TLayerProperty> : IDataBinding
{
private TProperty _currentValue = default!;
private readonly List<IDataBindingProperty> _properties = new();
private bool _disposed;
private TimeSpan _easingProgress;
private TProperty _lastAppliedValue = default!;
private TProperty _previousValue = default!;
private bool _reapplyValue;
private bool _isEnabled;
internal DataBinding(DataBindingRegistration<TLayerProperty, TProperty> dataBindingRegistration)
internal DataBinding(LayerProperty<TLayerProperty> layerProperty)
{
LayerProperty = dataBindingRegistration.LayerProperty;
Entity = new DataBindingEntity();
LayerProperty = layerProperty;
ApplyRegistration(dataBindingRegistration);
Script = new NodeScript<TProperty>(GetScriptName(), "The value to put into the data binding", LayerProperty.ProfileElement.Profile);
Entity = new DataBindingEntity();
Script = new DataBindingNodeScript<TLayerProperty>(GetScriptName(), "The value to put into the data binding", this, LayerProperty.ProfileElement.Profile);
Save();
}
@@ -27,39 +26,23 @@ namespace Artemis.Core
internal DataBinding(LayerProperty<TLayerProperty> layerProperty, DataBindingEntity entity)
{
LayerProperty = layerProperty;
Entity = entity;
Script = new NodeScript<TProperty>(GetScriptName(), "The value to put into the data binding", LayerProperty.ProfileElement.Profile);
Script = new DataBindingNodeScript<TLayerProperty>(GetScriptName(), "The value to put into the data binding", this, LayerProperty.ProfileElement.Profile);
// Load will add children so be initialized before that
Load();
}
/// <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" />
/// Gets the script used to populate the data binding
/// </summary>
public DataBindingConverter<TLayerProperty, TProperty>? Converter { get; private set; }
public NodeScript<TProperty> Script { 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; }
public DataBindingNodeScript<TLayerProperty> Script { get; private set; }
/// <summary>
/// Gets the data binding entity this data binding uses for persistent storage
@@ -67,38 +50,52 @@ namespace Artemis.Core
public DataBindingEntity Entity { get; }
/// <summary>
/// Gets the current value of the data binding
/// Updates the pending values of this 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)
public void Update()
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
if (Converter == null)
return baseValue;
if (!IsEnabled)
return;
// TODO: Update the base node
Script.Run();
// If no easing is to be applied simple return whatever the current value is
if (EasingTime == TimeSpan.Zero || !Converter.SupportsInterpolate)
return Script.Result;
// If the value changed, update the current and previous values used for easing
if (!Equals(Script.Result, _currentValue))
ResetEasing(Script.Result);
// Apply interpolation between the previous and current value
return GetInterpolatedValue();
}
/// <summary>
/// Returns the type of the target property of this data binding
/// Registers a data binding property so that is available to the data binding system
/// </summary>
public Type? GetTargetType()
/// <typeparam name="TProperty">The type of the layer property</typeparam>
/// <param name="getter">The function to call to get the value of the property</param>
/// <param name="setter">The action to call to set the value of the property</param>
/// <param name="displayName">The display name of the data binding property</param>
public DataBindingProperty<TProperty> RegisterDataBindingProperty<TProperty>(Func<TProperty> getter, Action<TProperty?> setter, string displayName)
{
return Registration?.Getter.Method.ReturnType;
if (_disposed)
throw new ObjectDisposedException("DataBinding");
if (Properties.Any(d => d.DisplayName == displayName))
throw new ArtemisCoreException($"A databinding property named '{displayName}' is already registered.");
DataBindingProperty<TProperty> property = new(getter, setter, displayName);
_properties.Add(property);
OnDataBindingPropertyRegistered();
return property;
}
/// <summary>
/// Removes all data binding properties so they are no longer available to the data binding system
/// </summary>
public void ClearDataBindingProperties()
{
if (_disposed)
throw new ObjectDisposedException("LayerProperty");
_properties.Clear();
OnDataBindingPropertiesCleared();
}
/// <summary>
@@ -113,111 +110,78 @@ namespace Artemis.Core
if (disposing)
{
_disposed = true;
if (Registration != null)
Registration.DataBinding = null;
Script.Dispose();
}
}
private void ResetEasing(TProperty value)
/// <summary>
/// Invokes the <see cref="DataBindingPropertyRegistered" /> event
/// </summary>
protected virtual void OnDataBindingPropertyRegistered()
{
_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));
DataBindingPropertyRegistered?.Invoke(this, new DataBindingEventArgs(this));
}
/// <summary>
/// Updates the smoothing progress of the data binding
/// Invokes the <see cref="DataBindingDisabled" /> event
/// </summary>
/// <param name="timeline">The timeline to apply during update</param>
public void Update(Timeline timeline)
protected virtual void OnDataBindingPropertiesCleared()
{
// 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 || timeline.IsOverridden)
return;
DataBindingPropertiesCleared?.Invoke(this, new DataBindingEventArgs(this));
}
UpdateWithDelta(timeline.Delta);
/// <summary>
/// Invokes the <see cref="DataBindingEnabled" /> event
/// </summary>
protected virtual void OnDataBindingEnabled(DataBindingEventArgs e)
{
DataBindingEnabled?.Invoke(this, e);
}
/// <summary>
/// Invokes the <see cref="DataBindingDisabled" /> event
/// </summary>
protected virtual void OnDataBindingDisabled(DataBindingEventArgs e)
{
DataBindingDisabled?.Invoke(this, e);
}
private string GetScriptName()
{
return LayerProperty.PropertyDescription.Name ?? LayerProperty.Path;
}
/// <inheritdoc />
public void UpdateWithDelta(TimeSpan delta)
public ILayerProperty BaseLayerProperty => LayerProperty;
/// <inheritdoc />
public bool IsEnabled
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
get => _isEnabled;
set
{
_isEnabled = value;
// 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;
if (_isEnabled)
OnDataBindingEnabled(new DataBindingEventArgs(this));
else
OnDataBindingDisabled(new DataBindingEventArgs(this));
}
}
/// <inheritdoc />
public ReadOnlyCollection<IDataBindingProperty> Properties => _properties.AsReadOnly();
/// <inheritdoc />
public void Apply()
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
if (Converter == null)
if (!IsEnabled)
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;
}
private string GetScriptName()
{
if (LayerProperty.GetAllDataBindingRegistrations().Count == 1)
return LayerProperty.PropertyDescription.Name ?? LayerProperty.Path;
return $"{LayerProperty.PropertyDescription.Name ?? LayerProperty.Path} - {Registration?.DisplayName}";
Script.DataBindingExitNode.ApplyToDataBinding();
}
/// <inheritdoc />
@@ -227,6 +191,19 @@ namespace Artemis.Core
GC.SuppressFinalize(this);
}
/// <inheritdoc />
public event EventHandler<DataBindingEventArgs>? DataBindingPropertyRegistered;
/// <inheritdoc />
public event EventHandler<DataBindingEventArgs>? DataBindingPropertiesCleared;
/// <inheritdoc />
public event EventHandler<DataBindingEventArgs>? DataBindingEnabled;
/// <inheritdoc />
public event EventHandler<DataBindingEventArgs>? DataBindingDisabled;
#region Storage
/// <inheritdoc />
@@ -234,14 +211,6 @@ namespace Artemis.Core
{
if (_disposed)
throw new ObjectDisposedException("DataBinding");
// General
DataBindingRegistration<TLayerProperty, TProperty>? registration = LayerProperty.GetDataBindingRegistration<TProperty>(Entity.Identifier);
if (registration != null)
ApplyRegistration(registration);
EasingTime = Entity.EasingTime;
EasingFunction = (Easings.Functions) Entity.EasingFunction;
}
/// <inheritdoc />
@@ -249,8 +218,8 @@ namespace Artemis.Core
{
Script.Dispose();
Script = Entity.NodeScript != null
? new NodeScript<TProperty>(GetScriptName(), "The value to put into the data binding", Entity.NodeScript, LayerProperty.ProfileElement.Profile)
: new NodeScript<TProperty>(GetScriptName(), "The value to put into the data binding", LayerProperty.ProfileElement.Profile);
? new DataBindingNodeScript<TLayerProperty>(GetScriptName(), "The value to put into the data binding", this, Entity.NodeScript, LayerProperty.ProfileElement.Profile)
: new DataBindingNodeScript<TLayerProperty>(GetScriptName(), "The value to put into the data binding", this, LayerProperty.ProfileElement.Profile);
}
/// <inheritdoc />
@@ -259,17 +228,7 @@ namespace Artemis.Core
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.Identifier = Registration.DisplayName;
Entity.EasingTime = EasingTime;
Entity.EasingFunction = (int) EasingFunction;
Script?.Save();
Script.Save();
Entity.NodeScript = Script?.Entity;
}
@@ -1,90 +0,0 @@
using System;
namespace Artemis.Core
{
/// <summary>
/// Represents a data binding converter that acts as the bridge between a
/// <see cref="DataBinding{TLayerProperty, TProperty}" /> and a <see cref="LayerProperty{T}" />
/// </summary>
public abstract class DataBindingConverter<TLayerProperty, TProperty> : IDataBindingConverter
{
/// <summary>
/// Gets the data binding this converter is applied to
/// </summary>
public DataBinding<TLayerProperty, TProperty>? DataBinding { get; private set; }
/// <summary>
/// Gets whether or not this data binding converter supports the <see cref="Sum" /> method
/// </summary>
public bool SupportsSum { get; protected set; }
/// <summary>
/// Gets whether or not this data binding converter supports the <see cref="Interpolate" /> method
/// </summary>
public bool SupportsInterpolate { get; protected set; }
/// <summary>
/// Returns the sum of <paramref name="a" /> and <paramref name="b" />
/// </summary>
public abstract TProperty Sum(TProperty a, TProperty b);
/// <summary>
/// Returns the the interpolated value between <paramref name="a" /> and <paramref name="b" /> on a scale (generally)
/// between <c>0.0</c> and <c>1.0</c> defined by the <paramref name="progress" />
/// <para>Note: The progress may go be negative or go beyond <c>1.0</c> depending on the easing method used</para>
/// </summary>
/// <param name="a">The value to interpolate away from</param>
/// <param name="b">The value to interpolate towards</param>
/// <param name="progress">The progress of the interpolation between 0.0 and 1.0</param>
/// <returns></returns>
public abstract TProperty Interpolate(TProperty a, TProperty b, double progress);
/// <summary>
/// Applies the <paramref name="value" /> to the layer property
/// </summary>
/// <param name="value"></param>
public virtual void ApplyValue(TProperty value)
{
if (DataBinding?.Registration == null)
throw new ArtemisCoreException("Data binding converter is not yet initialized");
DataBinding.Registration.Setter(value);
}
/// <summary>
/// Returns the current base value of the data binding
/// </summary>
public virtual TProperty GetValue()
{
if (DataBinding?.Registration == null)
throw new ArtemisCoreException("Data binding converter is not yet initialized");
return DataBinding.Registration.Getter();
}
/// <summary>
/// Converts the provided object to a type of <typeparamref name="TProperty" />
/// </summary>
public virtual TProperty ConvertFromObject(object? source)
{
return (TProperty) Convert.ChangeType(source, typeof(TProperty))!;
}
/// <summary>
/// Called when the data binding converter has been initialized and the <see cref="DataBinding" /> is available
/// </summary>
protected virtual void OnInitialized()
{
}
internal void Initialize(DataBinding<TLayerProperty, TProperty> dataBinding)
{
if (dataBinding.Registration == null)
throw new ArtemisCoreException("Cannot initialize a data binding converter for a data binding without a registration");
DataBinding = dataBinding;
OnInitialized();
}
/// <inheritdoc />
public Type SupportedType => typeof(TProperty);
}
}
@@ -0,0 +1,46 @@
using System;
namespace Artemis.Core
{
/// <inheritdoc />
public class DataBindingProperty<TProperty> : IDataBindingProperty
{
internal DataBindingProperty(Func<TProperty> getter, Action<TProperty?> setter, string displayName)
{
Getter = getter ?? throw new ArgumentNullException(nameof(getter));
Setter = setter ?? throw new ArgumentNullException(nameof(setter));
DisplayName = displayName ?? throw new ArgumentNullException(nameof(displayName));
}
/// <summary>
/// Gets the function to call to get the value of the property
/// </summary>
public Func<TProperty> Getter { get; }
/// <summary>
/// Gets the action to call to set the value of the property
/// </summary>
public Action<TProperty?> Setter { get; }
/// <inheritdoc />
public string DisplayName { get; }
/// <inheritdoc />
public Type ValueType => typeof(TProperty);
/// <inheritdoc />
public object? GetValue()
{
return Getter();
}
/// <inheritdoc />
public void SetValue(object? value)
{
if (value is TProperty match)
Setter(match);
else
throw new ArgumentException("Value must match the type of the data binding registration", nameof(value));
}
}
}
@@ -1,87 +0,0 @@
using System;
using System.Linq;
using Artemis.Storage.Entities.Profile.DataBindings;
namespace Artemis.Core
{
/// <inheritdoc />
public class DataBindingRegistration<TLayerProperty, TProperty> : IDataBindingRegistration
{
internal DataBindingRegistration(LayerProperty<TLayerProperty> layerProperty, DataBindingConverter<TLayerProperty, TProperty> converter,
Func<TProperty> getter, Action<TProperty> setter, string displayName)
{
LayerProperty = layerProperty ?? throw new ArgumentNullException(nameof(layerProperty));
Converter = converter ?? throw new ArgumentNullException(nameof(converter));
Getter = getter ?? throw new ArgumentNullException(nameof(getter));
Setter = setter ?? throw new ArgumentNullException(nameof(setter));
DisplayName = displayName ?? throw new ArgumentNullException(nameof(displayName));
}
/// <summary>
/// Gets the layer property this registration was made on
/// </summary>
public LayerProperty<TLayerProperty> LayerProperty { get; }
/// <summary>
/// Gets the converter that's used by the data binding
/// </summary>
public DataBindingConverter<TLayerProperty, TProperty> Converter { get; }
/// <summary>
/// Gets the function to call to get the value of the property
/// </summary>
public Func<TProperty> Getter { get; }
/// <summary>
/// Gets the action to call to set the value of the property
/// </summary>
public Action<TProperty> Setter { get; }
/// <inheritdoc />
public string DisplayName { get; }
/// <inheritdoc />
public Type ValueType => typeof(TProperty);
/// <summary>
/// Gets the data binding created using this registration
/// </summary>
public DataBinding<TLayerProperty, TProperty>? DataBinding { get; internal set; }
/// <inheritdoc />
public IDataBinding? GetDataBinding()
{
return DataBinding;
}
/// <inheritdoc />
public IDataBinding? CreateDataBinding()
{
if (DataBinding != null)
return DataBinding;
DataBindingEntity? dataBinding = LayerProperty.Entity.DataBindingEntities.FirstOrDefault(e => e.Identifier == DisplayName);
if (dataBinding == null)
return null;
DataBinding = new DataBinding<TLayerProperty, TProperty>(LayerProperty, dataBinding);
return DataBinding;
}
/// <inheritdoc />
public void ClearDataBinding()
{
if (DataBinding == null)
return;
// The related entity is left behind, just in case the data binding is added back later
LayerProperty.DisableDataBinding(DataBinding);
}
/// <inheritdoc />
public object? GetValue()
{
return Getter();
}
}
}
@@ -1,4 +1,5 @@
using System;
using System.Collections.ObjectModel;
using Artemis.Core.Modules;
namespace Artemis.Core
@@ -7,22 +8,51 @@ namespace Artemis.Core
/// Represents a data binding that binds a certain <see cref="LayerProperty{T}" /> to a value inside a
/// <see cref="DataModel" />
/// </summary>
public interface IDataBinding : IStorageModel, IUpdateModel, IDisposable
public interface IDataBinding : IStorageModel, IDisposable
{
/// <summary>
/// Updates the smoothing progress of the data binding and recalculates the value next <see cref="Apply" /> call
/// Gets the layer property the data binding is applied to
/// </summary>
/// <param name="delta">The delta to apply during update</param>
void UpdateWithDelta(TimeSpan delta);
ILayerProperty BaseLayerProperty { get; }
/// <summary>
/// Applies the data binding to the layer property
/// Gets a list of sub-properties this data binding applies to
/// </summary>
ReadOnlyCollection<IDataBindingProperty> Properties { get; }
/// <summary>
/// Gets a boolean indicating whether the data binding is enabled or not
/// </summary>
bool IsEnabled { get; set; }
/// <summary>
/// Applies the pending value of the data binding to the property
/// </summary>
void Apply();
/// <summary>
/// If the data binding is enabled, loads the node script for that data binding
/// </summary>
void LoadNodeScript();
/// <summary>
/// Occurs when a data binding property has been added
/// </summary>
public event EventHandler<DataBindingEventArgs>? DataBindingPropertyRegistered;
/// <summary>
/// Occurs when all data binding properties have been removed
/// </summary>
public event EventHandler<DataBindingEventArgs>? DataBindingPropertiesCleared;
/// <summary>
/// Occurs when a data binding has been enabled
/// </summary>
public event EventHandler<DataBindingEventArgs>? DataBindingEnabled;
/// <summary>
/// Occurs when a data binding has been disabled
/// </summary>
public event EventHandler<DataBindingEventArgs>? DataBindingDisabled;
}
}
@@ -1,16 +0,0 @@
using System;
namespace Artemis.Core
{
/// <summary>
/// Represents a data binding converter that acts as the bridge between a
/// <see cref="DataBinding{TLayerProperty, TProperty}" /> and a <see cref="LayerProperty{T}" />
/// </summary>
public interface IDataBindingConverter
{
/// <summary>
/// Gets the type this converter supports
/// </summary>
public Type SupportedType { get; }
}
}
@@ -5,7 +5,7 @@ namespace Artemis.Core
/// <summary>
/// Represents a data binding registration
/// </summary>
public interface IDataBindingRegistration
public interface IDataBindingProperty
{
/// <summary>
/// Gets or sets the display name of the data binding registration
@@ -18,25 +18,15 @@ namespace Artemis.Core
Type ValueType { get; }
/// <summary>
/// Returns the data binding applied using this registration
/// </summary>
public IDataBinding? GetDataBinding();
/// <summary>
/// If found, creates a data binding from storage
/// </summary>
/// <returns></returns>
IDataBinding? CreateDataBinding();
/// <summary>
/// If present, removes the current data binding
/// </summary>
void ClearDataBinding();
/// <summary>
/// Gets the value of the data binding
/// Gets the value of the property this registration points to
/// </summary>
/// <returns>A value matching the type of <see cref="ValueType" /></returns>
object? GetValue();
/// <summary>
/// Sets the value of the property this registration points to
/// </summary>
/// <param name="value">A value matching the type of <see cref="ValueType" /></param>
void SetValue(object? value);
}
}
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using Artemis.Storage.Entities.Profile;
namespace Artemis.Core
@@ -23,6 +22,21 @@ namespace Artemis.Core
/// </summary>
LayerPropertyGroup LayerPropertyGroup { get; }
/// <summary>
/// Gets the data binding of this property
/// </summary>
IDataBinding BaseDataBinding { get; }
/// <summary>
/// Gets a boolean indicating whether the layer has any data binding properties
/// </summary>
public bool HasDataBinding { get; }
/// <summary>
/// Gets a boolean indicating whether data bindings are supported on this type of property
/// </summary>
public bool DataBindingsSupported { get; }
/// <summary>
/// Gets the unique path of the property on the layer
/// </summary>
@@ -47,11 +61,6 @@ namespace Artemis.Core
/// </summary>
void Initialize(RenderProfileElement profileElement, LayerPropertyGroup group, PropertyEntity entity, bool fromStorage, PropertyDescriptionAttribute description, string path);
/// <summary>
/// Returns a list off all data binding registrations
/// </summary>
List<IDataBindingRegistration> GetAllDataBindingRegistrations();
/// <summary>
/// Attempts to load and add the provided keyframe entity to the layer property
/// </summary>
@@ -70,6 +79,12 @@ namespace Artemis.Core
/// <param name="timeline">The timeline to apply to the property</param>
void Update(Timeline timeline);
/// <summary>
/// Updates just the data binding instead of the entire layer
/// </summary>
void UpdateDataBinding();
/// <summary>
/// Occurs when the layer property is disposed
/// </summary>
@@ -104,25 +119,5 @@ namespace Artemis.Core
/// Occurs when a keyframe was removed from the layer property
/// </summary>
public event EventHandler<LayerPropertyEventArgs>? KeyframeRemoved;
/// <summary>
/// Occurs when a data binding property has been added
/// </summary>
public event EventHandler<LayerPropertyEventArgs>? DataBindingPropertyRegistered;
/// <summary>
/// Occurs when all data binding properties have been removed
/// </summary>
public event EventHandler<LayerPropertyEventArgs>? DataBindingPropertiesCleared;
/// <summary>
/// Occurs when a data binding has been enabled
/// </summary>
public event EventHandler<LayerPropertyEventArgs>? DataBindingEnabled;
/// <summary>
/// Occurs when a data binding has been disabled
/// </summary>
public event EventHandler<LayerPropertyEventArgs>? DataBindingDisabled;
}
}
@@ -30,6 +30,7 @@ namespace Artemis.Core
Path = null!;
Entity = null!;
PropertyDescription = null!;
DataBinding = null!;
CurrentValue = default!;
DefaultValue = default!;
@@ -57,93 +58,10 @@ namespace Artemis.Core
{
_disposed = true;
foreach (IDataBinding dataBinding in _dataBindings)
dataBinding.Dispose();
DataBinding.Dispose();
Disposed?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Invokes the <see cref="Updated" /> event
/// </summary>
protected virtual void OnUpdated()
{
Updated?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="CurrentValueSet" /> event
/// </summary>
protected virtual void OnCurrentValueSet()
{
CurrentValueSet?.Invoke(this, new LayerPropertyEventArgs(this));
LayerPropertyGroup.OnLayerPropertyOnCurrentValueSet(new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="VisibilityChanged" /> event
/// </summary>
protected virtual void OnVisibilityChanged()
{
VisibilityChanged?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="KeyframesToggled" /> event
/// </summary>
protected virtual void OnKeyframesToggled()
{
KeyframesToggled?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="KeyframeAdded" /> event
/// </summary>
protected virtual void OnKeyframeAdded()
{
KeyframeAdded?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="KeyframeRemoved" /> event
/// </summary>
protected virtual void OnKeyframeRemoved()
{
KeyframeRemoved?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="DataBindingPropertyRegistered" /> event
/// </summary>
protected virtual void OnDataBindingPropertyRegistered()
{
DataBindingPropertyRegistered?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="DataBindingDisabled" /> event
/// </summary>
protected virtual void OnDataBindingPropertiesCleared()
{
DataBindingPropertiesCleared?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="DataBindingEnabled" /> event
/// </summary>
protected virtual void OnDataBindingEnabled(LayerPropertyEventArgs e)
{
DataBindingEnabled?.Invoke(this, e);
}
/// <summary>
/// Invokes the <see cref="DataBindingDisabled" /> event
/// </summary>
protected virtual void OnDataBindingDisabled(LayerPropertyEventArgs e)
{
DataBindingDisabled?.Invoke(this, e);
}
/// <inheritdoc />
public PropertyDescriptionAttribute PropertyDescription { get; internal set; }
@@ -162,7 +80,16 @@ namespace Artemis.Core
CurrentValue = BaseValue;
UpdateKeyframes(timeline);
UpdateDataBindings(timeline);
UpdateDataBinding();
// UpdateDataBinding called OnUpdated()
}
/// <inheritdoc />
public void UpdateDataBinding()
{
DataBinding.Update();
DataBinding.Apply();
OnUpdated();
}
@@ -174,39 +101,6 @@ namespace Artemis.Core
GC.SuppressFinalize(this);
}
/// <inheritdoc />
public event EventHandler? Disposed;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? Updated;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? CurrentValueSet;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? VisibilityChanged;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? KeyframesToggled;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? KeyframeAdded;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? KeyframeRemoved;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? DataBindingPropertyRegistered;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? DataBindingPropertiesCleared;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? DataBindingEnabled;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? DataBindingDisabled;
#region Hierarchy
private bool _isHidden;
@@ -480,137 +374,19 @@ namespace Artemis.Core
#region Data bindings
// ReSharper disable InconsistentNaming
internal readonly List<IDataBindingRegistration> _dataBindingRegistrations = new();
internal readonly List<IDataBinding> _dataBindings = new();
// ReSharper restore InconsistentNaming
/// <summary>
/// Gets whether data bindings are supported on this type of property
/// Gets the data binding of this property
/// </summary>
public bool DataBindingsSupported { get; protected internal set; } = true;
public DataBinding<T> DataBinding { get; private set; }
/// <summary>
/// Gets whether the layer has any active data bindings
/// </summary>
public bool HasDataBinding => GetAllDataBindingRegistrations().Any(r => r.GetDataBinding() != null);
/// <inheritdoc />
public bool DataBindingsSupported => DataBinding.Properties.Any();
/// <summary>
/// Gets a data binding registration by the display name used to register it
/// <para>Note: The expression must exactly match the one used to register the data binding</para>
/// </summary>
public DataBindingRegistration<T, TProperty>? GetDataBindingRegistration<TProperty>(string identifier)
{
if (_disposed)
throw new ObjectDisposedException("LayerProperty");
/// <inheritdoc />
public IDataBinding BaseDataBinding => DataBinding;
IDataBindingRegistration? match = _dataBindingRegistrations.FirstOrDefault(r => r is DataBindingRegistration<T, TProperty> registration &&
registration.DisplayName == identifier);
return (DataBindingRegistration<T, TProperty>?) match;
}
/// <summary>
/// Gets a list containing all data binding registrations of this layer property
/// </summary>
/// <returns>A list containing all data binding registrations of this layer property</returns>
public List<IDataBindingRegistration> GetAllDataBindingRegistrations()
{
return _dataBindingRegistrations;
}
/// <summary>
/// Registers a data binding property so that is available to the data binding system
/// </summary>
/// <typeparam name="TProperty">The type of the layer property</typeparam>
/// <param name="getter">The function to call to get the value of the property</param>
/// <param name="setter">The action to call to set the value of the property</param>
/// <param name="converter">The converter to use while applying the data binding</param>
/// <param name="displayName">The display name of the data binding property</param>
public DataBindingRegistration<T, TProperty> RegisterDataBindingProperty<TProperty>(Func<TProperty> getter, Action<TProperty> setter, DataBindingConverter<T, TProperty> converter,
string displayName)
{
if (_disposed)
throw new ObjectDisposedException("LayerProperty");
if (_dataBindingRegistrations.Any(d => d.DisplayName == displayName))
throw new ArtemisCoreException($"A databinding property named '{displayName}' is already registered.");
DataBindingRegistration<T, TProperty> registration = new(this, converter, getter, setter, displayName);
_dataBindingRegistrations.Add(registration);
// If not yet initialized, load the data binding related to the registration if available
if (_isInitialized)
{
IDataBinding? dataBinding = registration.CreateDataBinding();
if (dataBinding != null)
_dataBindings.Add(dataBinding);
}
OnDataBindingPropertyRegistered();
return registration;
}
/// <summary>
/// Removes all data binding properties so they are no longer available to the data binding system
/// </summary>
public void ClearDataBindingProperties()
{
if (_disposed)
throw new ObjectDisposedException("LayerProperty");
foreach (IDataBindingRegistration dataBindingRegistration in _dataBindingRegistrations)
dataBindingRegistration.ClearDataBinding();
_dataBindingRegistrations.Clear();
OnDataBindingPropertiesCleared();
}
/// <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 (_disposed)
throw new ObjectDisposedException("LayerProperty");
if (dataBindingRegistration.LayerProperty != this)
throw new ArtemisCoreException("Cannot enable a data binding using a data binding registration of a different layer property");
if (dataBindingRegistration.DataBinding != null)
throw new ArtemisCoreException("Provided data binding registration already has an enabled data binding");
DataBinding<T, TProperty> dataBinding = new(dataBindingRegistration);
_dataBindings.Add(dataBinding);
OnDataBindingEnabled(new LayerPropertyEventArgs(dataBinding.LayerProperty));
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)
{
if (_disposed)
throw new ObjectDisposedException("LayerProperty");
_dataBindings.Remove(dataBinding);
if (dataBinding.Registration != null)
dataBinding.Registration.DataBinding = null;
dataBinding.Dispose();
OnDataBindingDisabled(new LayerPropertyEventArgs(dataBinding.LayerProperty));
}
private void UpdateDataBindings(Timeline timeline)
{
foreach (IDataBinding dataBinding in _dataBindings)
{
dataBinding.Update(timeline);
dataBinding.Apply();
}
}
/// <inheritdoc />
public bool HasDataBinding => DataBinding.IsEnabled;
#endregion
@@ -694,6 +470,7 @@ namespace Artemis.Core
Entity = entity ?? throw new ArgumentNullException(nameof(entity));
PropertyDescription = description ?? throw new ArgumentNullException(nameof(description));
IsLoadedFromStorage = fromStorage;
DataBinding = new DataBinding<T>(this);
if (PropertyDescription.DisableKeyframes)
KeyframesSupported = false;
@@ -737,13 +514,7 @@ namespace Artemis.Core
// ignored for now
}
_dataBindings.Clear();
foreach (IDataBindingRegistration dataBindingRegistration in _dataBindingRegistrations)
{
IDataBinding? dataBinding = dataBindingRegistration.CreateDataBinding();
if (dataBinding != null)
_dataBindings.Add(dataBinding);
}
DataBinding.Load();
}
/// <summary>
@@ -762,9 +533,8 @@ namespace Artemis.Core
Entity.KeyframeEntities.Clear();
Entity.KeyframeEntities.AddRange(Keyframes.Select(k => k.GetKeyframeEntity()));
Entity.DataBindingEntities.Clear();
foreach (IDataBinding dataBinding in _dataBindings)
dataBinding.Save();
DataBinding.Save();
Entity.DataBinding = DataBinding.Entity;
}
/// <summary>
@@ -775,5 +545,79 @@ namespace Artemis.Core
}
#endregion
#region Events
/// <inheritdoc />
public event EventHandler? Disposed;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? Updated;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? CurrentValueSet;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? VisibilityChanged;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? KeyframesToggled;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? KeyframeAdded;
/// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? KeyframeRemoved;
/// <summary>
/// Invokes the <see cref="Updated" /> event
/// </summary>
protected virtual void OnUpdated()
{
Updated?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="CurrentValueSet" /> event
/// </summary>
protected virtual void OnCurrentValueSet()
{
CurrentValueSet?.Invoke(this, new LayerPropertyEventArgs(this));
LayerPropertyGroup.OnLayerPropertyOnCurrentValueSet(new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="VisibilityChanged" /> event
/// </summary>
protected virtual void OnVisibilityChanged()
{
VisibilityChanged?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="KeyframesToggled" /> event
/// </summary>
protected virtual void OnKeyframesToggled()
{
KeyframesToggled?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="KeyframeAdded" /> event
/// </summary>
protected virtual void OnKeyframeAdded()
{
KeyframeAdded?.Invoke(this, new LayerPropertyEventArgs(this));
}
/// <summary>
/// Invokes the <see cref="KeyframeRemoved" /> event
/// </summary>
protected virtual void OnKeyframeRemoved()
{
KeyframeRemoved?.Invoke(this, new LayerPropertyEventArgs(this));
}
#endregion
}
}
@@ -113,11 +113,8 @@ namespace Artemis.Core
? new NodeScript<bool>($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active", RenderElementEntity.NodeScript, Profile)
: new NodeScript<bool>($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active", Profile);
foreach (ILayerProperty layerProperty in GetAllLayerProperties())
{
foreach (IDataBindingRegistration dataBindingRegistration in layerProperty.GetAllDataBindingRegistrations())
dataBindingRegistration.GetDataBinding()?.LoadNodeScript();
}
foreach (ILayerProperty layerProperty in GetAllLayerProperties())
layerProperty.BaseDataBinding.LoadNodeScript();
}
internal void OnLayerEffectsUpdated()