mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-12 03:48:56 +00:00
Core - Layer refactor WIP
This commit is contained in:
1 parent
be897b99f7
commit
d37420e462
33 files changed
+941
-895
No files matched your search
@@ -1,273 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// For internal use only, to implement your own layer property type, extend <see cref="LayerProperty{T}" /> instead.
|
||||
/// </summary>
|
||||
public abstract class BaseLayerProperty
|
||||
{
|
||||
protected readonly List<DataBindingRegistration> _dataBindingRegistrations = new List<DataBindingRegistration>();
|
||||
protected readonly List<DataBinding> _dataBindings = new List<DataBinding>();
|
||||
|
||||
private object _baseValue;
|
||||
private object _currentValue;
|
||||
private object _defaultValue;
|
||||
private bool _isHidden;
|
||||
private bool _keyframesEnabled;
|
||||
|
||||
internal BaseLayerProperty()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the base value of this layer property without any keyframes applied
|
||||
/// </summary>
|
||||
public object BaseValue
|
||||
{
|
||||
get => _baseValue;
|
||||
set
|
||||
{
|
||||
if (value != null && value.GetType() != GetPropertyType())
|
||||
throw new ArtemisCoreException("Cannot update base value because of a type mismatch");
|
||||
_baseValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current value of this property as it is affected by it's keyframes, updated once every frame
|
||||
/// </summary>
|
||||
public object CurrentValue
|
||||
{
|
||||
get => _currentValue;
|
||||
set
|
||||
{
|
||||
if (value != null && value.GetType() != GetPropertyType())
|
||||
throw new ArtemisCoreException("Cannot update current value because of a type mismatch");
|
||||
_currentValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 object DefaultValue
|
||||
{
|
||||
get => _defaultValue;
|
||||
set
|
||||
{
|
||||
if (value != null && value.GetType() != GetPropertyType())
|
||||
throw new ArtemisCoreException("Cannot update default value because of a type mismatch");
|
||||
_defaultValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list containing the active data bindings
|
||||
/// </summary>
|
||||
public IReadOnlyList<DataBinding> DataBindings => _dataBindings.AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list containing all the data binding registrations
|
||||
/// </summary>
|
||||
public IReadOnlyList<DataBindingRegistration> DataBindingRegistrations => _dataBindingRegistrations.AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile element (such as layer or folder) this effect is applied to
|
||||
/// </summary>
|
||||
public RenderProfileElement ProfileElement { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The parent group of this layer property, set after construction
|
||||
/// </summary>
|
||||
public LayerPropertyGroup Parent { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether keyframes are supported on this type of property
|
||||
/// </summary>
|
||||
public bool KeyframesSupported { get; protected internal set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether data bindings are supported on this type of property
|
||||
/// </summary>
|
||||
public bool DataBindingsSupported { 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 or sets whether the property is hidden in the UI
|
||||
/// </summary>
|
||||
public bool IsHidden
|
||||
{
|
||||
get => _isHidden;
|
||||
set
|
||||
{
|
||||
_isHidden = value;
|
||||
OnVisibilityChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <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; }
|
||||
|
||||
/// <summary>
|
||||
/// Used to declare that this property doesn't belong to a plugin and should use the core plugin GUID
|
||||
/// </summary>
|
||||
public bool IsCoreProperty { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description attribute applied to this property
|
||||
/// </summary>
|
||||
public PropertyDescriptionAttribute PropertyDescription { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all the keyframes in their non-generic base form, without their values being available
|
||||
/// </summary>
|
||||
public abstract IReadOnlyList<BaseLayerPropertyKeyframe> BaseKeyframes { get; }
|
||||
|
||||
internal PropertyEntity PropertyEntity { get; set; }
|
||||
internal LayerPropertyGroup LayerPropertyGroup { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the property value with the default value
|
||||
/// </summary>
|
||||
public abstract void ApplyDefaultValue();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the type of the property
|
||||
/// </summary>
|
||||
public abstract Type GetPropertyType();
|
||||
|
||||
/// <summary>
|
||||
/// Applies the provided property entity to the layer property by deserializing the JSON base value and keyframe values
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <param name="layerPropertyGroup"></param>
|
||||
/// <param name="fromStorage"></param>
|
||||
internal abstract void ApplyToLayerProperty(PropertyEntity entity, LayerPropertyGroup layerPropertyGroup, bool fromStorage);
|
||||
|
||||
/// <summary>
|
||||
/// Saves the property to the underlying property entity that was configured when calling
|
||||
/// <see cref="ApplyToLayerProperty" />
|
||||
/// </summary>
|
||||
internal abstract void ApplyToEntity();
|
||||
|
||||
#region Data bindings
|
||||
|
||||
internal void InitializeDataBindings(IDataModelService dataModelService, IDataBindingService dataBindingService)
|
||||
{
|
||||
foreach (var dataBinding in DataBindings)
|
||||
dataBinding.Initialize(dataModelService, dataBindingService);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new data binding targeting the given property to the <see cref="DataBindings" /> collection
|
||||
/// </summary>
|
||||
/// <returns>The newly created data binding</returns>
|
||||
public DataBinding EnableDataBinding(DataBindingRegistration 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(dataBindingRegistration);
|
||||
_dataBindings.Add(dataBinding);
|
||||
|
||||
return dataBinding;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the provided data binding from the <see cref="DataBindings" /> collection
|
||||
/// </summary>
|
||||
/// <param name="dataBinding">The data binding to remove</param>
|
||||
public void DisableDataBinding(DataBinding dataBinding)
|
||||
{
|
||||
_dataBindings.Remove(dataBinding);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Occurs once every frame when the layer property is updated
|
||||
/// </summary>
|
||||
public event EventHandler<LayerPropertyEventArgs> Updated;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the base value of the layer property was updated
|
||||
/// </summary>
|
||||
public event EventHandler<LayerPropertyEventArgs> BaseValueChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the <see cref="IsHidden" /> value of the layer property was updated
|
||||
/// </summary>
|
||||
public event EventHandler<LayerPropertyEventArgs> VisibilityChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when keyframes are enabled/disabled
|
||||
/// </summary>
|
||||
public event EventHandler<LayerPropertyEventArgs> KeyframesToggled;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a new keyframe was added to the layer property
|
||||
/// </summary>
|
||||
public event EventHandler<LayerPropertyEventArgs> KeyframeAdded;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a keyframe was removed from the layer property
|
||||
/// </summary>
|
||||
public event EventHandler<LayerPropertyEventArgs> KeyframeRemoved;
|
||||
|
||||
protected virtual void OnUpdated()
|
||||
{
|
||||
Updated?.Invoke(this, new LayerPropertyEventArgs(this));
|
||||
}
|
||||
|
||||
protected virtual void OnBaseValueChanged()
|
||||
{
|
||||
BaseValueChanged?.Invoke(this, new LayerPropertyEventArgs(this));
|
||||
}
|
||||
|
||||
protected virtual void OnVisibilityChanged()
|
||||
{
|
||||
VisibilityChanged?.Invoke(this, new LayerPropertyEventArgs(this));
|
||||
}
|
||||
|
||||
protected virtual void OnKeyframesToggled()
|
||||
{
|
||||
KeyframesToggled?.Invoke(this, new LayerPropertyEventArgs(this));
|
||||
}
|
||||
|
||||
protected virtual void OnKeyframeAdded()
|
||||
{
|
||||
KeyframeAdded?.Invoke(this, new LayerPropertyEventArgs(this));
|
||||
}
|
||||
|
||||
protected virtual void OnKeyframeRemoved()
|
||||
{
|
||||
KeyframeRemoved?.Invoke(this, new LayerPropertyEventArgs(this));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using System;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// For internal use only, use <see cref="LayerPropertyKeyframe{T}" /> instead.
|
||||
/// </summary>
|
||||
public abstract class BaseLayerPropertyKeyframe : PropertyChangedBase
|
||||
{
|
||||
internal BaseLayerPropertyKeyframe(BaseLayerProperty baseLayerProperty)
|
||||
{
|
||||
BaseLayerProperty = baseLayerProperty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The base class of the layer property this keyframe is applied to
|
||||
/// </summary>
|
||||
public BaseLayerProperty BaseLayerProperty { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The position of this keyframe in the timeline
|
||||
/// </summary>
|
||||
public abstract TimeSpan Position { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The easing function applied on the value of the keyframe
|
||||
/// </summary>
|
||||
public Easings.Functions EasingFunction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Removes the keyframe from the layer property
|
||||
/// </summary>
|
||||
public abstract void Remove();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a property on a layer. Properties are saved in storage and can optionally be modified from the UI.
|
||||
/// <para>
|
||||
/// Note: You cannot initialize layer properties yourself. If properly placed and annotated, the Artemis core will
|
||||
/// initialize these for you.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public interface ILayerProperty : IStorageModel, IUpdateModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the layer property
|
||||
/// <para>
|
||||
/// Note: This isn't done in the constructor to keep it parameterless which is easier for implementations of
|
||||
/// <see cref="LayerProperty{T}" />
|
||||
/// </para>
|
||||
/// </summary>
|
||||
void Initialize(RenderProfileElement profileElement, LayerPropertyGroup group, PropertyEntity entity, bool fromStorage, PropertyDescriptionAttribute description);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,28 @@
|
||||
using System;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
public class LayerPropertyKeyframe<T> : BaseLayerPropertyKeyframe
|
||||
/// <summary>
|
||||
/// Represents a keyframe on a <see cref="LayerProperty{T}" /> containing a value and a timestamp
|
||||
/// </summary>
|
||||
public class LayerPropertyKeyframe<T> : PropertyChangedBase
|
||||
{
|
||||
private LayerProperty<T> _layerProperty;
|
||||
private TimeSpan _position;
|
||||
private T _value;
|
||||
|
||||
public LayerPropertyKeyframe(T value, TimeSpan position, Easings.Functions easingFunction, LayerProperty<T> layerProperty) : base(layerProperty)
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="LayerPropertyKeyframe{T}" /> class
|
||||
/// </summary>
|
||||
/// <param name="value">The value of the keyframe</param>
|
||||
/// <param name="position">The position of this keyframe in the timeline</param>
|
||||
/// <param name="easingFunction">The easing function applied on the value of the keyframe</param>
|
||||
/// <param name="layerProperty">The layer property this keyframe is applied to</param>
|
||||
public LayerPropertyKeyframe(T value, TimeSpan position, Easings.Functions easingFunction, LayerProperty<T> layerProperty)
|
||||
{
|
||||
_position = position;
|
||||
|
||||
Value = value;
|
||||
LayerProperty = layerProperty;
|
||||
EasingFunction = easingFunction;
|
||||
@@ -34,8 +46,11 @@ namespace Artemis.Core
|
||||
set => SetAndNotify(ref _value, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override TimeSpan Position
|
||||
|
||||
/// <summary>
|
||||
/// The position of this keyframe in the timeline
|
||||
/// </summary>
|
||||
public TimeSpan Position
|
||||
{
|
||||
get => _position;
|
||||
set
|
||||
@@ -45,8 +60,15 @@ namespace Artemis.Core
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Remove()
|
||||
/// <summary>
|
||||
/// The easing function applied on the value of the keyframe
|
||||
/// </summary>
|
||||
public Easings.Functions EasingFunction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Removes the keyframe from the layer property
|
||||
/// </summary>
|
||||
public void Remove()
|
||||
{
|
||||
LayerProperty.RemoveKeyframe(this);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class ColorGradientLayerProperty : LayerProperty<ColorGradient>
|
||||
@@ -9,10 +7,12 @@ namespace Artemis.Core
|
||||
{
|
||||
KeyframesSupported = false;
|
||||
DataBindingsSupported = false;
|
||||
|
||||
BaseValueChanged += OnBaseValueChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="ColorGradientLayerProperty" /> to a <see cref="ColorGradient"/>
|
||||
/// Implicitly converts an <see cref="ColorGradientLayerProperty" /> to a <see cref="ColorGradient" />
|
||||
/// </summary>
|
||||
public static implicit operator ColorGradient(ColorGradientLayerProperty p)
|
||||
{
|
||||
@@ -25,12 +25,11 @@ namespace Artemis.Core
|
||||
throw new ArtemisCoreException("Color Gradients do not support keyframes.");
|
||||
}
|
||||
|
||||
internal override void ApplyToLayerProperty(PropertyEntity entity, LayerPropertyGroup layerPropertyGroup, bool fromStorage)
|
||||
private void OnBaseValueChanged(object sender, LayerPropertyEventArgs<ColorGradient> e)
|
||||
{
|
||||
base.ApplyToLayerProperty(entity, layerPropertyGroup, fromStorage);
|
||||
|
||||
// Don't allow color gradients to be null
|
||||
BaseValue ??= DefaultValue ?? new ColorGradient();
|
||||
if (BaseValue == null)
|
||||
BaseValue = DefaultValue ?? new ColorGradient();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,10 @@ namespace Artemis.Core
|
||||
{
|
||||
internal SKColorLayerProperty()
|
||||
{
|
||||
RegisterDataBindingProperty(color => color.Alpha, new SKColorPartDataBindingConverter(SKColorPartDataBindingConverter.Channel.Alpha));
|
||||
RegisterDataBindingProperty(color => color.Red, new SKColorPartDataBindingConverter(SKColorPartDataBindingConverter.Channel.Red));
|
||||
RegisterDataBindingProperty(color => color.Green, new SKColorPartDataBindingConverter(SKColorPartDataBindingConverter.Channel.Green));
|
||||
RegisterDataBindingProperty(color => color.Blue, new SKColorPartDataBindingConverter(SKColorPartDataBindingConverter.Channel.Blue));
|
||||
RegisterDataBindingProperty(color => color.Hue, new SKColorPartDataBindingConverter(SKColorPartDataBindingConverter.Channel.Hue));
|
||||
RegisterDataBindingProperty(color => color.Alpha, new SKColorArgbDataBindingConverter(SKColorArgbDataBindingConverter.Channel.Alpha));
|
||||
RegisterDataBindingProperty(color => color.Red, new SKColorArgbDataBindingConverter(SKColorArgbDataBindingConverter.Channel.Red));
|
||||
RegisterDataBindingProperty(color => color.Green, new SKColorArgbDataBindingConverter(SKColorArgbDataBindingConverter.Channel.Green));
|
||||
RegisterDataBindingProperty(color => color.Blue, new SKColorArgbDataBindingConverter(SKColorArgbDataBindingConverter.Channel.Blue));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -7,8 +7,8 @@ namespace Artemis.Core
|
||||
{
|
||||
internal SKPointLayerProperty()
|
||||
{
|
||||
RegisterDataBindingProperty(point => point.X, new FloatDataBindingConverter());
|
||||
RegisterDataBindingProperty(point => point.Y, new FloatDataBindingConverter());
|
||||
RegisterDataBindingProperty(point => point.X, new FloatDataBindingConverter<SKPoint>());
|
||||
RegisterDataBindingProperty(point => point.Y, new FloatDataBindingConverter<SKPoint>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -7,8 +7,8 @@ namespace Artemis.Core
|
||||
{
|
||||
internal SKSizeLayerProperty()
|
||||
{
|
||||
RegisterDataBindingProperty(size => size.Width, new FloatDataBindingConverter());
|
||||
RegisterDataBindingProperty(size => size.Height, new FloatDataBindingConverter());
|
||||
RegisterDataBindingProperty(size => size.Width, new FloatDataBindingConverter<SKSize>());
|
||||
RegisterDataBindingProperty(size => size.Height, new FloatDataBindingConverter<SKSize>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in new issue
Block a user