From d37420e46275ec2957f9260a31a6c1358d5cdf58 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 8 Sep 2020 19:17:04 +0200 Subject: [PATCH] Core - Layer refactor WIP --- .../Events/LayerPropertyEventArgs.cs | 16 +- src/Artemis.Core/Models/IStorageModel.cs | 18 + src/Artemis.Core/Models/IUpdateModel.cs | 14 + .../Converters/FloatDataBindingConverter.cs | 37 +- .../Converters/GeneralDataBindingConverter.cs | 3 +- .../Converters/IntDataBindingConverter.cs | 29 +- .../SKColorArgbDataBindingConverter.cs | 81 +++ .../SKColorPartDataBindingConverter.cs | 95 ---- .../Profile/DataBindings/DataBinding.cs | 184 +++---- .../DataBindings/DataBindingConverter.cs | 72 +-- .../DataBindings/DataBindingModifier.cs | 31 +- .../DataBindings/DataBindingRegistration.cs | 54 +- .../Profile/DataBindings/IDataBinding.cs | 23 + .../DataBindings/IDataBindingConverter.cs | 16 + .../DataBindings/IDataBindingModifier.cs | 9 + .../DataBindings/IDataBindingRegistration.cs | 14 + src/Artemis.Core/Models/Profile/Layer.cs | 8 +- .../LayerProperties/BaseLayerProperty.cs | 273 ---------- .../BaseLayerPropertyKeyframe.cs | 36 -- .../Profile/LayerProperties/ILayerProperty.cs | 23 + .../Profile/LayerProperties/LayerProperty.cs | 508 ++++++++++++------ .../LayerProperties/LayerPropertyKeyFrame.cs | 34 +- .../Types/ColorGradientLayerProperty.cs | 15 +- .../Types/SKColorLayerProperty.cs | 9 +- .../Types/SKPointLayerProperty.cs | 4 +- .../Types/SKSizeLayerProperty.cs | 4 +- .../Models/Profile/LayerPropertyGroup.cs | 196 +++---- .../Internal/PropertiesLayerBrush.cs | 5 +- .../Plugins/LayerEffects/LayerEffect.cs | 4 +- .../Services/DataBindingService.cs | 9 +- .../Interfaces/IDataBindingService.cs | 4 +- .../Services/RenderElementService.cs | 4 +- .../Services/Storage/ProfileService.cs | 4 +- 33 files changed, 941 insertions(+), 895 deletions(-) create mode 100644 src/Artemis.Core/Models/IStorageModel.cs create mode 100644 src/Artemis.Core/Models/IUpdateModel.cs create mode 100644 src/Artemis.Core/Models/Profile/DataBindings/Converters/Internal/SKColorArgbDataBindingConverter.cs delete mode 100644 src/Artemis.Core/Models/Profile/DataBindings/Converters/Internal/SKColorPartDataBindingConverter.cs create mode 100644 src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs create mode 100644 src/Artemis.Core/Models/Profile/DataBindings/IDataBindingConverter.cs create mode 100644 src/Artemis.Core/Models/Profile/DataBindings/IDataBindingModifier.cs create mode 100644 src/Artemis.Core/Models/Profile/DataBindings/IDataBindingRegistration.cs delete mode 100644 src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs delete mode 100644 src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerPropertyKeyframe.cs create mode 100644 src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs diff --git a/src/Artemis.Core/Events/LayerPropertyEventArgs.cs b/src/Artemis.Core/Events/LayerPropertyEventArgs.cs index c469947d0..296de8310 100644 --- a/src/Artemis.Core/Events/LayerPropertyEventArgs.cs +++ b/src/Artemis.Core/Events/LayerPropertyEventArgs.cs @@ -2,13 +2,23 @@ namespace Artemis.Core { - public class LayerPropertyEventArgs : EventArgs + public class LayerPropertyEventArgs : EventArgs { - public LayerPropertyEventArgs(BaseLayerProperty layerProperty) + public LayerPropertyEventArgs(LayerProperty layerProperty) { LayerProperty = layerProperty; } - public BaseLayerProperty LayerProperty { get; } + public LayerProperty LayerProperty { get; } + } + + public class LayerPropertyEventArgs : EventArgs + { + public LayerPropertyEventArgs(ILayerProperty layerProperty) + { + LayerProperty = layerProperty; + } + + public ILayerProperty LayerProperty { get; } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/IStorageModel.cs b/src/Artemis.Core/Models/IStorageModel.cs new file mode 100644 index 000000000..ce76eb0cd --- /dev/null +++ b/src/Artemis.Core/Models/IStorageModel.cs @@ -0,0 +1,18 @@ +namespace Artemis.Core +{ + /// + /// Represents a model that can be loaded and saved to persistent storage + /// + public interface IStorageModel + { + /// + /// Loads the model from its associated entity + /// + void Load(); + + /// + /// Saves the model to its associated entity + /// + void Save(); + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/IUpdateModel.cs b/src/Artemis.Core/Models/IUpdateModel.cs new file mode 100644 index 000000000..870079a7f --- /dev/null +++ b/src/Artemis.Core/Models/IUpdateModel.cs @@ -0,0 +1,14 @@ +namespace Artemis.Core +{ + /// + /// Represents a model that updates using a delta time + /// + public interface IUpdateModel + { + /// + /// Performs an update on the model + /// + /// The delta time in seconds + void Update(double deltaTime); + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/Converters/FloatDataBindingConverter.cs b/src/Artemis.Core/Models/Profile/DataBindings/Converters/FloatDataBindingConverter.cs index eb337ba54..e480ccc18 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/Converters/FloatDataBindingConverter.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/Converters/FloatDataBindingConverter.cs @@ -3,46 +3,51 @@ namespace Artemis.Core { /// - public class FloatDataBindingConverter : DataBindingConverter + public class FloatDataBindingConverter : FloatDataBindingConverter { + } + + /// + /// The type of layer property this converter is applied to + public class FloatDataBindingConverter : DataBindingConverter + { + /// + /// Creates a new instance of the class + /// public FloatDataBindingConverter() { - SupportedType = typeof(float); SupportsSum = true; SupportsInterpolate = true; } /// - public override object Sum(object a, object b) + public override float Sum(float a, float b) { - return Convert.ToSingle(a) + Convert.ToSingle(b); + return a + b; } /// - public override object Interpolate(object a, object b, double progress) + public override float Interpolate(float a, float b, double progress) { - var floatA = Convert.ToSingle(a); - var floatB = Convert.ToSingle(b); - var diff = floatB - floatA; - return floatA + diff * progress; + var diff = a - b; + return (float) (a + diff * progress); } /// - public override void ApplyValue(object value) + public override void ApplyValue(float value) { - var floatValue = Convert.ToSingle(value); if (DataBinding.LayerProperty.PropertyDescription.MaxInputValue is float max) - floatValue = Math.Min(floatValue, max); + value = Math.Min(value, max); if (DataBinding.LayerProperty.PropertyDescription.MinInputValue is float min) - floatValue = Math.Max(floatValue, min); + value = Math.Max(value, min); - ValueSetter?.Invoke(floatValue); + ValueSetter?.Invoke(value); } /// - public override object GetValue() + public override float GetValue() { - return ValueGetter?.Invoke(); + return ValueGetter?.Invoke() ?? 0f; } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/Converters/GeneralDataBindingConverter.cs b/src/Artemis.Core/Models/Profile/DataBindings/Converters/GeneralDataBindingConverter.cs index 561ef8784..839d3a14b 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/Converters/GeneralDataBindingConverter.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/Converters/GeneralDataBindingConverter.cs @@ -3,11 +3,10 @@ namespace Artemis.Core { /// - public class GeneralDataBindingConverter : DataBindingConverter + public class GeneralDataBindingConverter : DataBindingConverter where T : ILayerProperty { public GeneralDataBindingConverter() { - SupportedType = typeof(object); SupportsSum = false; SupportsInterpolate = false; } diff --git a/src/Artemis.Core/Models/Profile/DataBindings/Converters/IntDataBindingConverter.cs b/src/Artemis.Core/Models/Profile/DataBindings/Converters/IntDataBindingConverter.cs index c3466a43f..0c452e08d 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/Converters/IntDataBindingConverter.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/Converters/IntDataBindingConverter.cs @@ -3,11 +3,18 @@ namespace Artemis.Core { /// - public class IntDataBindingConverter : DataBindingConverter + public class IntDataBindingConverter : FloatDataBindingConverter { + } + + /// + public class IntDataBindingConverter : DataBindingConverter where T : ILayerProperty + { + /// + /// Creates a new instance of the class + /// public IntDataBindingConverter() { - SupportedType = typeof(int); SupportsSum = true; SupportsInterpolate = true; } @@ -19,30 +26,28 @@ namespace Artemis.Core public MidpointRounding InterpolationRoundingMode { get; set; } = MidpointRounding.AwayFromZero; /// - public override object Sum(object a, object b) + public override int Sum(int a, int b) { - return (int) a + (int) b; + return a + b; } /// - public override object Interpolate(object a, object b, double progress) + public override int Interpolate(int a, int b, double progress) { - var intA = (int) a; - var intB = (int) b; - var diff = intB - intA; - return (int) Math.Round(intA + diff * progress, InterpolationRoundingMode); + var diff = b - a; + return (int) Math.Round(a + diff * progress, InterpolationRoundingMode); } /// - public override void ApplyValue(object value) + public override void ApplyValue(int value) { ValueSetter?.Invoke(value); } /// - public override object GetValue() + public override int GetValue() { - return ValueGetter?.Invoke(); + return ValueGetter?.Invoke() ?? 0; } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/Converters/Internal/SKColorArgbDataBindingConverter.cs b/src/Artemis.Core/Models/Profile/DataBindings/Converters/Internal/SKColorArgbDataBindingConverter.cs new file mode 100644 index 000000000..cc8c6c551 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/DataBindings/Converters/Internal/SKColorArgbDataBindingConverter.cs @@ -0,0 +1,81 @@ +using System; +using SkiaSharp; + +namespace Artemis.Core +{ + // This is internal because it's mainly a proof-of-concept + internal class SKColorArgbDataBindingConverter : DataBindingConverter + { + private readonly Channel _channel; + + public SKColorArgbDataBindingConverter(Channel channel) + { + _channel = channel; + + SupportsSum = true; + SupportsInterpolate = true; + } + + public override byte Sum(byte a, byte b) + { + return ClampToByte(a + b); + } + + public override byte Interpolate(byte a, byte b, double progress) + { + var diff = b - a; + return ClampToByte(diff * progress); + } + + public override void ApplyValue(byte value) + { + switch (_channel) + { + case Channel.Alpha: + DataBinding.LayerProperty.CurrentValue = DataBinding.LayerProperty.CurrentValue.WithAlpha(value); + break; + case Channel.Red: + DataBinding.LayerProperty.CurrentValue = DataBinding.LayerProperty.CurrentValue.WithRed(value); + break; + case Channel.Green: + DataBinding.LayerProperty.CurrentValue = DataBinding.LayerProperty.CurrentValue.WithGreen(value); + break; + case Channel.Blue: + DataBinding.LayerProperty.CurrentValue = DataBinding.LayerProperty.CurrentValue.WithBlue(value); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + public override byte GetValue() + { + switch (_channel) + { + case Channel.Alpha: + return DataBinding.LayerProperty.CurrentValue.Alpha; + case Channel.Red: + return DataBinding.LayerProperty.CurrentValue.Red; + case Channel.Green: + return DataBinding.LayerProperty.CurrentValue.Green; + case Channel.Blue: + return DataBinding.LayerProperty.CurrentValue.Blue; + default: + throw new ArgumentOutOfRangeException(); + } + } + + private static byte ClampToByte(double value) + { + return (byte) Math.Clamp(value, 0, 255); + } + + public enum Channel + { + Alpha, + Red, + Green, + Blue + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/Converters/Internal/SKColorPartDataBindingConverter.cs b/src/Artemis.Core/Models/Profile/DataBindings/Converters/Internal/SKColorPartDataBindingConverter.cs deleted file mode 100644 index 3073a9721..000000000 --- a/src/Artemis.Core/Models/Profile/DataBindings/Converters/Internal/SKColorPartDataBindingConverter.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System; -using SkiaSharp; - -namespace Artemis.Core -{ - // This is internal because it's mainly a proof-of-concept - internal class SKColorPartDataBindingConverter : DataBindingConverter - { - private readonly Channel _channel; - - public SKColorPartDataBindingConverter(Channel channel) - { - _channel = channel; - - SupportsSum = true; - SupportsInterpolate = true; - SupportedType = _channel switch - { - Channel.Alpha => typeof(byte), - Channel.Red => typeof(byte), - Channel.Green => typeof(byte), - Channel.Blue => typeof(byte), - Channel.Hue => typeof(float), - _ => throw new ArgumentOutOfRangeException() - }; - } - - public override object Sum(object a, object b) - { - return (float) a + (float) b; - } - - public override object Interpolate(object a, object b, double progress) - { - var diff = (float) b - (float) a; - return diff * progress; - } - - public override void ApplyValue(object value) - { - var property = (SKColorLayerProperty) DataBinding.LayerProperty; - switch (_channel) - { - case Channel.Alpha: - property.CurrentValue = property.CurrentValue.WithAlpha((byte) value); - break; - case Channel.Red: - property.CurrentValue = property.CurrentValue.WithRed((byte) value); - break; - case Channel.Green: - property.CurrentValue = property.CurrentValue.WithGreen((byte) value); - break; - case Channel.Blue: - property.CurrentValue = property.CurrentValue.WithBlue((byte) value); - break; - case Channel.Hue: - property.CurrentValue.ToHsv(out var h, out var s, out var v); - property.CurrentValue = SKColor.FromHsv((float) value, s, v); - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - - public override object GetValue() - { - var property = (SKColorLayerProperty) DataBinding.LayerProperty; - switch (_channel) - { - case Channel.Alpha: - return property.CurrentValue.Alpha; - case Channel.Red: - return property.CurrentValue.Red; - case Channel.Green: - return property.CurrentValue.Green; - case Channel.Blue: - return property.CurrentValue.Blue; - case Channel.Hue: - property.CurrentValue.ToHsv(out var h, out _, out _); - return h; - default: - throw new ArgumentOutOfRangeException(); - } - } - - public enum Channel - { - Alpha, - Red, - Green, - Blue, - Hue - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs index f28a911f5..d3f8bd9f3 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs @@ -9,48 +9,46 @@ using Artemis.Storage.Entities.Profile.DataBindings; namespace Artemis.Core { - /// - /// A data binding that binds a certain to a value inside a - /// - public class DataBinding + /// + public class DataBinding : IDataBinding { - private readonly List _modifiers = new List(); + private readonly List> _modifiers = new List>(); - private object _currentValue; - private object _previousValue; + private TProperty _currentValue; private TimeSpan _easingProgress; + private TProperty _previousValue; - internal DataBinding(DataBindingRegistration dataBindingRegistration) + internal DataBinding(DataBindingRegistration dataBindingRegistration) { LayerProperty = dataBindingRegistration.LayerProperty; Entity = new DataBindingEntity(); ApplyRegistration(dataBindingRegistration); - ApplyToEntity(); + Save(); } - internal DataBinding(BaseLayerProperty layerProperty, DataBindingEntity entity) + internal DataBinding(LayerProperty layerProperty, DataBindingEntity entity) { LayerProperty = layerProperty; Entity = entity; - ApplyToDataBinding(); + Load(); } - /// - /// Gets the layer property this data binding targets - /// - public BaseLayerProperty LayerProperty { get; } - /// /// Gets the data binding registration this data binding is based upon /// - public DataBindingRegistration Registration { get; private set; } + 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; } + public DataBindingConverter Converter { get; private set; } /// /// Gets the property on the this data binding targets @@ -82,7 +80,7 @@ namespace Artemis.Core /// /// Gets a list of modifiers applied to this data binding /// - public IReadOnlyList Modifiers => _modifiers.AsReadOnly(); + public IReadOnlyList> Modifiers => _modifiers.AsReadOnly(); /// /// Gets the compiled function that gets the current value of the data binding target @@ -91,10 +89,24 @@ namespace Artemis.Core internal DataBindingEntity Entity { get; } + /// + /// Updates the smoothing progress of the data binding + /// + /// The time in seconds that passed since the last update + public void Update(double deltaTime) + { + // Data bindings cannot go back in time like brushes + deltaTime = Math.Max(0, deltaTime); + + _easingProgress = _easingProgress.Add(TimeSpan.FromSeconds(deltaTime)); + if (_easingProgress > EasingTime) + _easingProgress = EasingTime; + } + /// /// Adds a modifier to the data binding's collection /// - public void AddModifier(DataBindingModifier modifier) + public void AddModifier(DataBindingModifier modifier) { if (!_modifiers.Contains(modifier)) { @@ -108,7 +120,7 @@ namespace Artemis.Core /// /// Removes a modifier from the data binding's collection /// - public void RemoveModifier(DataBindingModifier modifier) + public void RemoveModifier(DataBindingModifier modifier) { if (_modifiers.Contains(modifier)) { @@ -147,7 +159,7 @@ namespace Artemis.Core /// /// The base value of the property the data binding is applied to /// - public object GetValue(object baseValue) + public TProperty GetValue(TProperty baseValue) { if (CompiledTargetAccessor == null || Converter == null) return baseValue; @@ -156,7 +168,7 @@ namespace Artemis.Core foreach (var dataBindingModifier in Modifiers) dataBindingValue = dataBindingModifier.Apply(dataBindingValue); - var value = Convert.ChangeType(dataBindingValue, TargetProperty.PropertyType); + var value = (TProperty) Convert.ChangeType(dataBindingValue, typeof(TProperty)); // If no easing is to be applied simple return whatever the current value is if (EasingTime == TimeSpan.Zero || !Converter.SupportsInterpolate) @@ -170,13 +182,6 @@ namespace Artemis.Core return GetInterpolatedValue(); } - private void ResetEasing(object value) - { - _previousValue = GetInterpolatedValue(); - _currentValue = value; - _easingProgress = TimeSpan.Zero; - } - /// /// Returns the type of the target property of this data binding /// @@ -193,24 +198,8 @@ namespace Artemis.Core return SourceDataModel?.GetTypeAtPath(SourcePropertyPath); } - /// - /// Updates the smoothing progress of the data binding - /// - /// The time in seconds that passed since the last update - public void Update(double deltaTime) - { - // Data bindings cannot go back in time like brushes - deltaTime = Math.Max(0, deltaTime); - - _easingProgress = _easingProgress.Add(TimeSpan.FromSeconds(deltaTime)); - if (_easingProgress > EasingTime) - _easingProgress = EasingTime; - } - - /// - /// Updates the value on the according to the data binding - /// - public void ApplyToProperty() + /// + public void Apply() { if (Converter == null) return; @@ -219,44 +208,8 @@ namespace Artemis.Core Converter.ApplyValue(GetValue(value)); } - internal void ApplyToEntity() - { - // General - Entity.TargetProperty = TargetProperty?.Name; - Entity.DataBindingMode = (int) Mode; - Entity.EasingTime = EasingTime; - Entity.EasingFunction = (int) EasingFunction; - - // Data model - Entity.SourceDataModelGuid = SourceDataModel?.PluginInfo?.Guid; - Entity.SourcePropertyPath = SourcePropertyPath; - - // Modifiers - Entity.Modifiers.Clear(); - foreach (var dataBindingModifier in Modifiers) - { - dataBindingModifier.ApplyToEntity(); - Entity.Modifiers.Add(dataBindingModifier.Entity); - } - } - - internal void ApplyToDataBinding() - { - // General - ApplyRegistration(LayerProperty.DataBindingRegistrations.FirstOrDefault(p => p.Property.Name == Entity.TargetProperty)); - - Mode = (DataBindingMode) Entity.DataBindingMode; - EasingTime = Entity.EasingTime; - EasingFunction = (Easings.Functions) Entity.EasingFunction; - - // Data model is done during Initialize - - // Modifiers - foreach (var dataBindingModifierEntity in Entity.Modifiers) - _modifiers.Add(new DataBindingModifier(this, dataBindingModifierEntity)); - } - - internal void Initialize(IDataModelService dataModelService, IDataBindingService dataBindingService) + /// + public void Initialize(IDataModelService dataModelService, IDataBindingService dataBindingService) { // Source if (Entity.SourceDataModelGuid != null && SourceDataModel == null) @@ -271,7 +224,14 @@ namespace Artemis.Core dataBindingModifier.Initialize(dataModelService, dataBindingService); } - private void ApplyRegistration(DataBindingRegistration dataBindingRegistration) + private void ResetEasing(TProperty value) + { + _previousValue = GetInterpolatedValue(); + _currentValue = value; + _easingProgress = TimeSpan.Zero; + } + + private void ApplyRegistration(DataBindingRegistration dataBindingRegistration) { if (dataBindingRegistration != null) dataBindingRegistration.DataBinding = this; @@ -283,15 +243,15 @@ namespace Artemis.Core if (GetTargetType().IsValueType) { if (_currentValue == null) - _currentValue = GetTargetType().GetDefault(); + _currentValue = default; if (_previousValue == null) - _previousValue = _currentValue; + _previousValue = default; } Converter?.Initialize(this); } - private object GetInterpolatedValue() + private TProperty GetInterpolatedValue() { if (_easingProgress == EasingTime || !Converter.SupportsInterpolate) return _currentValue; @@ -318,6 +278,50 @@ namespace Artemis.Core CompiledTargetAccessor = lambda.Compile(); } + #region Storage + + /// + public void Load() + { + // General + var registration = LayerProperty.GetDataBindingRegistration(Entity.TargetProperty); + ApplyRegistration(registration); + + Mode = (DataBindingMode) Entity.DataBindingMode; + EasingTime = Entity.EasingTime; + EasingFunction = (Easings.Functions) Entity.EasingFunction; + + // Data model is done during Initialize + + // Modifiers + foreach (var dataBindingModifierEntity in Entity.Modifiers) + _modifiers.Add(new DataBindingModifier(this, dataBindingModifierEntity)); + } + + /// + public void Save() + { + if (!LayerProperty.Entity.DataBindingEntities.Contains(Entity)) + LayerProperty.Entity.DataBindingEntities.Add(Entity); + + // General + Entity.TargetProperty = TargetProperty?.Name; + Entity.DataBindingMode = (int) Mode; + Entity.EasingTime = EasingTime; + Entity.EasingFunction = (int) EasingFunction; + + // Data model + Entity.SourceDataModelGuid = SourceDataModel?.PluginInfo?.Guid; + Entity.SourcePropertyPath = SourcePropertyPath; + + // Modifiers + Entity.Modifiers.Clear(); + foreach (var dataBindingModifier in Modifiers) + dataBindingModifier.Save(); + } + + #endregion + #region Events /// diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs index 3ee8756d2..4e5194a24 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs @@ -1,29 +1,29 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using System.Reflection; namespace Artemis.Core { /// - /// A data binding converter that acts as the bridge between a and a - /// + /// Represents a data binding converter that acts as the bridge between a + /// and a /// - public abstract class DataBindingConverter + public abstract class DataBindingConverter : IDataBindingConverter { - internal Func ValueGetter { get; set; } - internal Action ValueSetter { get; set; } + /// + /// A dynamically compiled getter pointing to the data bound property + /// + public Func ValueGetter { get; private set; } + + /// + /// A dynamically compiled setter pointing to the data bound property + /// + public Action ValueSetter { get; private set; } /// /// Gets the data binding this converter is applied to /// - public DataBinding DataBinding { get; private set; } - - /// - /// Gets the type this converter supports - /// - public Type SupportedType { get; protected set; } + public DataBinding DataBinding { get; private set; } /// /// Gets whether or not this data binding converter supports the method @@ -35,17 +35,13 @@ namespace Artemis.Core /// public bool SupportsInterpolate { get; protected set; } - /// - /// Called when the data binding converter has been initialized and the is available - /// - protected virtual void OnInitialized() - { - } + /// + public Type SupportedType => typeof(TProperty); /// /// Returns the sum of and /// - public abstract object Sum(object a, object b); + public abstract TProperty Sum(TProperty a, TProperty b); /// /// Returns the the interpolated value between and on a scale (generally) @@ -56,20 +52,27 @@ namespace Artemis.Core /// The value to interpolate towards /// The progress of the interpolation between 0.0 and 1.0 /// - public abstract object Interpolate(object a, object b, double progress); + public abstract TProperty Interpolate(TProperty a, TProperty b, double progress); /// /// Applies the to the layer property /// /// - public abstract void ApplyValue(object value); + public abstract void ApplyValue(TProperty value); /// /// Returns the current base value of the data binding /// - public abstract object GetValue(); + public abstract TProperty GetValue(); - internal void Initialize(DataBinding dataBinding) + /// + /// Called when the data binding converter has been initialized and the is available + /// + protected virtual void OnInitialized() + { + } + + internal void Initialize(DataBinding dataBinding) { DataBinding = dataBinding; ValueGetter = CreateValueGetter(); @@ -78,7 +81,7 @@ namespace Artemis.Core OnInitialized(); } - private Func CreateValueGetter() + private Func CreateValueGetter() { if (DataBinding.TargetProperty?.DeclaringType == null) return null; @@ -86,21 +89,19 @@ namespace Artemis.Core var getterMethod = DataBinding.TargetProperty.GetGetMethod(); if (getterMethod == null) return null; - + var constant = Expression.Constant(DataBinding.LayerProperty); // The path is null if the registration is applied to the root (LayerProperty.CurrentValue) - var property = DataBinding.Registration.Path == null - ? Expression.Property(constant, DataBinding.TargetProperty) + var property = DataBinding.Registration.Path == null + ? Expression.Property(constant, DataBinding.TargetProperty) : (MemberExpression) DataBinding.Registration.Path.Split('.').Aggregate(constant, Expression.Property); - // The get method should cast to the object since it receives whatever type the property is - var body = Expression.Convert(property, typeof(object)); - var lambda = Expression.Lambda>(body); + var lambda = Expression.Lambda>(property); return lambda.Compile(); } - private Action CreateValueSetter() + private Action CreateValueSetter() { if (DataBinding.TargetProperty?.DeclaringType == null) return null; @@ -110,11 +111,10 @@ namespace Artemis.Core return null; var constant = Expression.Constant(DataBinding.LayerProperty); - var propertyValue = Expression.Parameter(typeof(object), "propertyValue"); + var propertyValue = Expression.Parameter(typeof(TProperty), "propertyValue"); - // The assign method should cast to the proper type since it receives an object - var body = Expression.Call(constant, setterMethod, Expression.Convert(propertyValue, DataBinding.TargetProperty.PropertyType)); - var lambda = Expression.Lambda>(body, propertyValue); + var body = Expression.Call(constant, setterMethod, propertyValue); + var lambda = Expression.Lambda>(body, propertyValue); return lambda.Compile(); } } diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingModifier.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingModifier.cs index 163c35ac4..18571fcde 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingModifier.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingModifier.cs @@ -8,37 +8,33 @@ using Newtonsoft.Json; namespace Artemis.Core { - /// - /// Modifies a data model value in a way defined by the modifier type - /// - public class DataBindingModifier + /// + public class DataBindingModifier : IDataBindingModifier { - private DataBinding _dataBinding; + private DataBinding _dataBinding; /// - /// Creates a new instance of the class + /// Creates a new instance of the class /// /// The type of the parameter, can either be dynamic (based on a data model value) or static public DataBindingModifier(ProfileRightSideType parameterType) { ParameterType = parameterType; Entity = new DataBindingModifierEntity(); - - ApplyToEntity(); + Save(); } - internal DataBindingModifier(DataBinding dataBinding, DataBindingModifierEntity entity) + internal DataBindingModifier(DataBinding dataBinding, DataBindingModifierEntity entity) { DataBinding = dataBinding; Entity = entity; - - ApplyToDataBindingModifier(); + Load(); } /// /// Gets the data binding this modifier is applied to /// - public DataBinding DataBinding + public DataBinding DataBinding { get => _dataBinding; internal set @@ -218,7 +214,7 @@ namespace Artemis.Core // If deserialization fails, use the type's default catch (JsonSerializationException e) { - dataBindingService.LogModifierDeserializationFailure(this, e); + dataBindingService.LogModifierDeserializationFailure(GetType().Name, e); staticValue = Activator.CreateInstance(targetType); } @@ -226,8 +222,12 @@ namespace Artemis.Core } } - internal void ApplyToEntity() + /// + public void Save() { + if (!DataBinding.Entity.Modifiers.Contains(Entity)) + DataBinding.Entity.Modifiers.Add(Entity); + // Modifier Entity.ModifierType = ModifierType?.GetType().Name; Entity.ModifierTypePluginGuid = ModifierType?.PluginInfo.Guid; @@ -242,7 +242,8 @@ namespace Artemis.Core Entity.ParameterStaticValue = JsonConvert.SerializeObject(ParameterStaticValue); } - internal void ApplyToDataBindingModifier() + /// + public void Load() { // Modifier type is done during Initialize diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs index f8d860bf2..9944d49b8 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs @@ -1,22 +1,62 @@ using System; +using System.Linq; using System.Reflection; namespace Artemis.Core { - public class DataBindingRegistration + /// + public class DataBindingRegistration : IDataBindingRegistration { - internal DataBindingRegistration(BaseLayerProperty layerProperty, PropertyInfo property, DataBindingConverter converter, string path) + internal DataBindingRegistration( + LayerProperty layerProperty, + DataBindingConverter converter, + PropertyInfo property, + string path) { LayerProperty = layerProperty ?? throw new ArgumentNullException(nameof(layerProperty)); - Property = property ?? throw new ArgumentNullException(nameof(property)); Converter = converter ?? throw new ArgumentNullException(nameof(converter)); - Path = path; + Property = property ?? throw new ArgumentNullException(nameof(property)); + Path = path ?? throw new ArgumentNullException(nameof(path)); } - public DataBinding DataBinding { get; internal set; } - public BaseLayerProperty LayerProperty { get; } + /// + /// Gets the layer property this registration was made on + /// + public LayerProperty LayerProperty { get; } + + + /// + /// Gets the converter that's used by the data binding + /// + public DataBindingConverter Converter { get; } + + /// + /// Gets the registered property + /// public PropertyInfo Property { get; } - public DataBindingConverter Converter { get; } + + /// + /// Gets the path of the registered property on the layer property + /// public string Path { get; } + + /// + /// Gets the data binding created using this registration + /// + public DataBinding DataBinding { get; internal set; } + + /// + public IDataBinding CreateDataBinding() + { + if (DataBinding != null) + return DataBinding; + + var dataBinding = LayerProperty.Entity.DataBindingEntities.FirstOrDefault(e => e.TargetProperty == Path); + if (dataBinding == null) + return null; + + DataBinding = new DataBinding(LayerProperty, dataBinding); + return DataBinding; + } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs new file mode 100644 index 000000000..6776a9e29 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs @@ -0,0 +1,23 @@ +using Artemis.Core.DataModelExpansions; +using Artemis.Core.Services; + +namespace Artemis.Core +{ + /// + /// Represents a data binding that binds a certain to a value inside a + /// + public interface IDataBinding : IStorageModel, IUpdateModel + { + /// + /// (Re)initializes the data binding + /// + /// + /// + void Initialize(IDataModelService dataModelService, IDataBindingService dataBindingService); + + /// + /// Applies the data binding to the layer property + /// + void Apply(); + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingConverter.cs b/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingConverter.cs new file mode 100644 index 000000000..9f207133f --- /dev/null +++ b/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingConverter.cs @@ -0,0 +1,16 @@ +using System; + +namespace Artemis.Core +{ + /// + /// Represents a data binding converter that acts as the bridge between a + /// and a + /// + public interface IDataBindingConverter + { + /// + /// Gets the type this converter supports + /// + public Type SupportedType { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingModifier.cs b/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingModifier.cs new file mode 100644 index 000000000..1192894eb --- /dev/null +++ b/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingModifier.cs @@ -0,0 +1,9 @@ +namespace Artemis.Core +{ + /// + /// Modifies a data model value in a way defined by the modifier type + /// + public interface IDataBindingModifier : IStorageModel + { + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingRegistration.cs b/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingRegistration.cs new file mode 100644 index 000000000..2bba9c36a --- /dev/null +++ b/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingRegistration.cs @@ -0,0 +1,14 @@ +namespace Artemis.Core +{ + /// + /// Represents a data binding registration + /// + public interface IDataBindingRegistration + { + /// + /// If found, creates a data binding from storage + /// + /// + IDataBinding CreateDataBinding(); + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index cad95b8d0..f58084cca 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -34,8 +34,8 @@ namespace Artemis.Core Name = name; Enabled = true; DisplayContinuously = true; - General = new LayerGeneralProperties {IsCorePropertyGroup = true}; - Transform = new LayerTransformProperties {IsCorePropertyGroup = true}; + General = new LayerGeneralProperties(); + Transform = new LayerTransformProperties(); _layerEffects = new List(); _leds = new List(); @@ -55,8 +55,8 @@ namespace Artemis.Core Name = layerEntity.Name; Enabled = layerEntity.Enabled; Order = layerEntity.Order; - General = new LayerGeneralProperties {IsCorePropertyGroup = true}; - Transform = new LayerTransformProperties {IsCorePropertyGroup = true}; + General = new LayerGeneralProperties(); + Transform = new LayerTransformProperties(); _layerEffects = new List(); _leds = new List(); diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs deleted file mode 100644 index 10f0a53f6..000000000 --- a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs +++ /dev/null @@ -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 -{ - /// - /// For internal use only, to implement your own layer property type, extend instead. - /// - public abstract class BaseLayerProperty - { - protected readonly List _dataBindingRegistrations = new List(); - protected readonly List _dataBindings = new List(); - - private object _baseValue; - private object _currentValue; - private object _defaultValue; - private bool _isHidden; - private bool _keyframesEnabled; - - internal BaseLayerProperty() - { - } - - /// - /// Gets or sets the base value of this layer property without any keyframes applied - /// - 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; - } - } - - /// - /// Gets the current value of this property as it is affected by it's keyframes, updated once every frame - /// - 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; - } - } - - /// - /// 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 - /// - 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; - } - } - - /// - /// Gets a list containing the active data bindings - /// - public IReadOnlyList DataBindings => _dataBindings.AsReadOnly(); - - /// - /// Gets a list containing all the data binding registrations - /// - public IReadOnlyList DataBindingRegistrations => _dataBindingRegistrations.AsReadOnly(); - - /// - /// Gets the profile element (such as layer or folder) this effect is applied to - /// - public RenderProfileElement ProfileElement { get; internal set; } - - /// - /// The parent group of this layer property, set after construction - /// - public LayerPropertyGroup Parent { get; internal set; } - - /// - /// Gets whether keyframes are supported on this type of property - /// - public bool KeyframesSupported { get; protected internal set; } = true; - - /// - /// Gets whether data bindings are supported on this type of property - /// - public bool DataBindingsSupported { get; protected internal set; } = true; - - /// - /// Gets or sets whether keyframes are enabled on this property, has no effect if is - /// False - /// - public bool KeyframesEnabled - { - get => _keyframesEnabled; - set - { - if (_keyframesEnabled == value) return; - _keyframesEnabled = value; - OnKeyframesToggled(); - } - } - - /// - /// Gets or sets whether the property is hidden in the UI - /// - public bool IsHidden - { - get => _isHidden; - set - { - _isHidden = value; - OnVisibilityChanged(); - } - } - - /// - /// Indicates whether the BaseValue was loaded from storage, useful to check whether a default value must be applied - /// - public bool IsLoadedFromStorage { get; internal set; } - - /// - /// Used to declare that this property doesn't belong to a plugin and should use the core plugin GUID - /// - public bool IsCoreProperty { get; internal set; } - - /// - /// Gets the description attribute applied to this property - /// - public PropertyDescriptionAttribute PropertyDescription { get; internal set; } - - /// - /// Gets a list of all the keyframes in their non-generic base form, without their values being available - /// - public abstract IReadOnlyList BaseKeyframes { get; } - - internal PropertyEntity PropertyEntity { get; set; } - internal LayerPropertyGroup LayerPropertyGroup { get; set; } - - /// - /// Overrides the property value with the default value - /// - public abstract void ApplyDefaultValue(); - - /// - /// Returns the type of the property - /// - public abstract Type GetPropertyType(); - - /// - /// Applies the provided property entity to the layer property by deserializing the JSON base value and keyframe values - /// - /// - /// - /// - internal abstract void ApplyToLayerProperty(PropertyEntity entity, LayerPropertyGroup layerPropertyGroup, bool fromStorage); - - /// - /// Saves the property to the underlying property entity that was configured when calling - /// - /// - internal abstract void ApplyToEntity(); - - #region Data bindings - - internal void InitializeDataBindings(IDataModelService dataModelService, IDataBindingService dataBindingService) - { - foreach (var dataBinding in DataBindings) - dataBinding.Initialize(dataModelService, dataBindingService); - } - - /// - /// Adds a new data binding targeting the given property to the collection - /// - /// The newly created data binding - 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; - } - - /// - /// Removes the provided data binding from the collection - /// - /// The data binding to remove - public void DisableDataBinding(DataBinding dataBinding) - { - _dataBindings.Remove(dataBinding); - } - - #endregion - - #region Events - - /// - /// Occurs once every frame when the layer property is updated - /// - public event EventHandler Updated; - - /// - /// Occurs when the base value of the layer property was updated - /// - public event EventHandler BaseValueChanged; - - /// - /// Occurs when the value of the layer property was updated - /// - public event EventHandler VisibilityChanged; - - /// - /// Occurs when keyframes are enabled/disabled - /// - public event EventHandler KeyframesToggled; - - /// - /// Occurs when a new keyframe was added to the layer property - /// - public event EventHandler KeyframeAdded; - - /// - /// Occurs when a keyframe was removed from the layer property - /// - public event EventHandler 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 - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerPropertyKeyframe.cs b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerPropertyKeyframe.cs deleted file mode 100644 index ced2050b9..000000000 --- a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerPropertyKeyframe.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using Stylet; - -namespace Artemis.Core -{ - /// - /// For internal use only, use instead. - /// - public abstract class BaseLayerPropertyKeyframe : PropertyChangedBase - { - internal BaseLayerPropertyKeyframe(BaseLayerProperty baseLayerProperty) - { - BaseLayerProperty = baseLayerProperty; - } - - /// - /// The base class of the layer property this keyframe is applied to - /// - public BaseLayerProperty BaseLayerProperty { get; internal set; } - - /// - /// The position of this keyframe in the timeline - /// - public abstract TimeSpan Position { get; set; } - - /// - /// The easing function applied on the value of the keyframe - /// - public Easings.Functions EasingFunction { get; set; } - - /// - /// Removes the keyframe from the layer property - /// - public abstract void Remove(); - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs new file mode 100644 index 000000000..ce10a1bd0 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs @@ -0,0 +1,23 @@ +using Artemis.Storage.Entities.Profile; + +namespace Artemis.Core +{ + /// + /// Represents a property on a layer. Properties are saved in storage and can optionally be modified from the UI. + /// + /// Note: You cannot initialize layer properties yourself. If properly placed and annotated, the Artemis core will + /// initialize these for you. + /// + /// + public interface ILayerProperty : IStorageModel, IUpdateModel + { + /// + /// Initializes the layer property + /// + /// Note: This isn't done in the constructor to keep it parameterless which is easier for implementations of + /// + /// + /// + void Initialize(RenderProfileElement profileElement, LayerPropertyGroup group, PropertyEntity entity, bool fromStorage, PropertyDescriptionAttribute description); + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs index ec44b97d3..acd4c9965 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs @@ -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 /// /// /// The type of property encapsulated in this layer property - public abstract class LayerProperty : BaseLayerProperty + public abstract class LayerProperty : ILayerProperty { - private bool _isInitialized; - private List> _keyframes; - + /// + /// Creates a new instance of the class + /// protected LayerProperty() { _keyframes = new List>(); } /// - /// Gets or sets the base value of this layer property without any keyframes applied + /// Gets the description attribute applied to this property /// - public new T BaseValue + public PropertyDescriptionAttribute PropertyDescription { get; internal set; } + + /// + /// Updates the property, applying keyframes and data bindings to the current value + /// + public void Update(double deltaTime) { - get => base.BaseValue != null ? (T) base.BaseValue : default; + CurrentValue = BaseValue; + + UpdateKeyframes(); + UpdateDataBindings(deltaTime); + + OnUpdated(); + } + + /// + /// Returns the type of the property + /// + public Type GetPropertyType() + { + return typeof(T); + } + + #region Hierarchy + + private bool _isHidden; + + /// + /// Gets or sets whether the property is hidden in the UI + /// + public bool IsHidden + { + get => _isHidden; set { - if (Equals(base.BaseValue, value)) + _isHidden = value; + OnVisibilityChanged(); + } + } + + /// + /// Gets the profile element (such as layer or folder) this property is applied to + /// + public RenderProfileElement ProfileElement { get; internal set; } + + /// + /// The parent group of this layer property, set after construction + /// + public LayerPropertyGroup LayerPropertyGroup { get; internal set; } + + #endregion + + #region Value management + + private T _baseValue; + + /// + /// Called every update (if keyframes are both supported and enabled) to determine the new + /// based on the provided progress + /// + /// The linear current keyframe progress + /// The current keyframe progress, eased with the current easing function + protected virtual void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased) + { + throw new NotImplementedException(); + } + + /// + /// Gets or sets the base value of this layer property without any keyframes applied + /// + 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)); } } /// /// Gets the current value of this property as it is affected by it's keyframes, updated once every frame /// - public new T CurrentValue - { - get => base.CurrentValue != null ? (T) base.CurrentValue : default; - set => base.CurrentValue = value; - } + public T CurrentValue { get; set; } /// /// 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 /// - public new T DefaultValue - { - get => base.DefaultValue != null ? (T) base.DefaultValue : default; - set => base.DefaultValue = value; - } - - /// - /// Gets a read-only list of all the keyframes on this layer property - /// - public IReadOnlyList> Keyframes => _keyframes.AsReadOnly(); - - /// - /// Gets the current keyframe in the timeline according to the current progress - /// - public LayerPropertyKeyframe CurrentKeyframe { get; protected set; } - - /// - /// Gets the next keyframe in the timeline according to the current progress - /// - public LayerPropertyKeyframe NextKeyframe { get; protected set; } - - public override IReadOnlyList BaseKeyframes => _keyframes.Cast().ToList().AsReadOnly(); + public T DefaultValue { get; set; } /// /// 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); } + /// + /// Overrides the property value with the default value + /// + public void ApplyDefaultValue() + { + BaseValue = DefaultValue; + CurrentValue = DefaultValue; + } + + #endregion + + #region Keyframes + + private bool _keyframesEnabled; + private List> _keyframes; + + /// + /// Gets whether keyframes are supported on this type of property + /// + public bool KeyframesSupported { get; protected internal set; } = true; + + /// + /// Gets or sets whether keyframes are enabled on this property, has no effect if is + /// False + /// + public bool KeyframesEnabled + { + get => _keyframesEnabled; + set + { + if (_keyframesEnabled == value) return; + _keyframesEnabled = value; + OnKeyframesToggled(); + } + } + + + /// + /// Gets a read-only list of all the keyframes on this layer property + /// + public IReadOnlyList> Keyframes => _keyframes.AsReadOnly(); + + /// + /// Gets the current keyframe in the timeline according to the current progress + /// + public LayerPropertyKeyframe CurrentKeyframe { get; protected set; } + + /// + /// Gets the next keyframe in the timeline according to the current progress + /// + public LayerPropertyKeyframe NextKeyframe { get; protected set; } + /// /// Adds a keyframe to the layer property /// @@ -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); } - /// - public override void ApplyDefaultValue() - { - BaseValue = DefaultValue; - CurrentValue = DefaultValue; - } - - /// - public override Type GetPropertyType() - { - return typeof(T); - } - - /// - /// Called every update (if keyframes are both supported and enabled) to determine the new - /// based on the provided progress - /// - /// The linear current keyframe progress - /// The current keyframe progress, eased with the current easing function - protected virtual void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased) - { - throw new NotImplementedException(); - } - - /// - /// Updates the property, applying keyframes and data bindings to the current value - /// - internal void Update(double deltaTime = 0) - { - CurrentValue = BaseValue; - - UpdateKeyframes(); - UpdateDataBindings(deltaTime); - - OnUpdated(); - } - /// /// Sorts the keyframes in ascending order by position /// @@ -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(entity.Value); - - IsLoadedFromStorage = fromStorage; - CurrentValue = BaseValue; - KeyframesEnabled = entity.KeyframesEnabled; - - _keyframes.Clear(); - _keyframes.AddRange(entity.KeyframeEntities.Select(k => new LayerPropertyKeyframe( - JsonConvert.DeserializeObject(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(Expression> propertyLambda, DataBindingConverter converter) + internal readonly List _dataBindingRegistrations = new List(); + internal readonly List _dataBindings = new List(); + + /// + /// Gets whether data bindings are supported on this type of property + /// + public bool DataBindingsSupported { get; protected internal set; } = true; + + public DataBindingRegistration GetDataBindingRegistration(string propertyName) + { + var match = _dataBindingRegistrations.FirstOrDefault(r => r is DataBindingRegistration registration && + registration.Property.Name == propertyName); + return (DataBindingRegistration) match; + } + + public void RegisterDataBindingProperty(Expression> propertyLambda, DataBindingConverter 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(this, converter, propertyInfo, path)); + } + + /// + /// Enables a data binding for the provided + /// + /// The newly created data binding + 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; + } + + /// + /// Disables the provided data binding + /// + /// The data binding to remove + public void DisableDataBinding(DataBinding 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; + + /// + /// Indicates whether the BaseValue was loaded from storage, useful to check whether a default value must be applied + /// + public bool IsLoadedFromStorage { get; internal set; } + + internal PropertyEntity Entity { get; set; } + + /// + 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); + } + + /// + 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(Entity.Value); + } + catch (JsonException e) + { + // ignored for now + } + } + + CurrentValue = BaseValue; + KeyframesEnabled = Entity.KeyframesEnabled; + + _keyframes.Clear(); + try + { + _keyframes.AddRange(Entity.KeyframeEntities.Select(k => new LayerPropertyKeyframe( + JsonConvert.DeserializeObject(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); + } + } + + /// + /// Saves the property to the underlying property entity that was configured when calling + /// + /// + 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 + + /// + /// Occurs once every frame when the layer property is updated + /// + public event EventHandler> Updated; + + /// + /// Occurs when the base value of the layer property was updated + /// + public event EventHandler> BaseValueChanged; + + /// + /// Occurs when the value of the layer property was updated + /// + public event EventHandler> VisibilityChanged; + + /// + /// Occurs when keyframes are enabled/disabled + /// + public event EventHandler> KeyframesToggled; + + /// + /// Occurs when a new keyframe was added to the layer property + /// + public event EventHandler> KeyframeAdded; + + /// + /// Occurs when a keyframe was removed from the layer property + /// + public event EventHandler> 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 } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs index 8ed7642bf..cdade5471 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs @@ -1,16 +1,28 @@ using System; +using Stylet; namespace Artemis.Core { - public class LayerPropertyKeyframe : BaseLayerPropertyKeyframe + /// + /// Represents a keyframe on a containing a value and a timestamp + /// + public class LayerPropertyKeyframe : PropertyChangedBase { private LayerProperty _layerProperty; private TimeSpan _position; private T _value; - public LayerPropertyKeyframe(T value, TimeSpan position, Easings.Functions easingFunction, LayerProperty layerProperty) : base(layerProperty) + /// + /// Creates a new instance of the class + /// + /// The value of the keyframe + /// The position of this keyframe in the timeline + /// The easing function applied on the value of the keyframe + /// The layer property this keyframe is applied to + public LayerPropertyKeyframe(T value, TimeSpan position, Easings.Functions easingFunction, LayerProperty layerProperty) { _position = position; + Value = value; LayerProperty = layerProperty; EasingFunction = easingFunction; @@ -34,8 +46,11 @@ namespace Artemis.Core set => SetAndNotify(ref _value, value); } - /// - public override TimeSpan Position + + /// + /// The position of this keyframe in the timeline + /// + public TimeSpan Position { get => _position; set @@ -45,8 +60,15 @@ namespace Artemis.Core } } - /// - public override void Remove() + /// + /// The easing function applied on the value of the keyframe + /// + public Easings.Functions EasingFunction { get; set; } + + /// + /// Removes the keyframe from the layer property + /// + public void Remove() { LayerProperty.RemoveKeyframe(this); } diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Types/ColorGradientLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Types/ColorGradientLayerProperty.cs index ab5d5a91c..fc1f58542 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/Types/ColorGradientLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Types/ColorGradientLayerProperty.cs @@ -1,6 +1,4 @@ -using Artemis.Storage.Entities.Profile; - -namespace Artemis.Core +namespace Artemis.Core { /// public class ColorGradientLayerProperty : LayerProperty @@ -9,10 +7,12 @@ namespace Artemis.Core { KeyframesSupported = false; DataBindingsSupported = false; + + BaseValueChanged += OnBaseValueChanged; } /// - /// Implicitly converts an to a + /// Implicitly converts an to a /// 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 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(); } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKColorLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKColorLayerProperty.cs index dc9032d9a..674086de7 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKColorLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKColorLayerProperty.cs @@ -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)); } /// diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKPointLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKPointLayerProperty.cs index 30194bfac..53484042f 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKPointLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKPointLayerProperty.cs @@ -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()); + RegisterDataBindingProperty(point => point.Y, new FloatDataBindingConverter()); } /// diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKSizeLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKSizeLayerProperty.cs index 59504ca18..b8f54a741 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKSizeLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKSizeLayerProperty.cs @@ -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()); + RegisterDataBindingProperty(size => size.Height, new FloatDataBindingConverter()); } /// diff --git a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs index 2d2a9231a..8f0119c50 100644 --- a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs +++ b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs @@ -2,54 +2,53 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Reflection; using Artemis.Core.LayerBrushes; using Artemis.Core.LayerEffects; using Artemis.Core.Properties; -using Artemis.Core.Services; using Artemis.Storage.Entities.Profile; namespace Artemis.Core { public abstract class LayerPropertyGroup : IDisposable { - private readonly List _layerProperties; + private readonly List _layerProperties; private readonly List _layerPropertyGroups; - private ReadOnlyCollection _allLayerProperties; private bool _isHidden; protected LayerPropertyGroup() { - _layerProperties = new List(); + _layerProperties = new List(); _layerPropertyGroups = new List(); } + public PropertyGroupDescriptionAttribute GroupDescription { get; internal set; } + /// - /// Gets the profile element (such as layer or folder) this effect is applied to + /// Gets the info of the plugin this group is associated with + /// + public PluginInfo PluginInfo { get; internal set; } + + /// + /// Gets the profile element (such as layer or folder) this group is associated with /// public RenderProfileElement ProfileElement { get; internal set; } + /// + /// The parent group of this group + /// + public LayerPropertyGroup Parent { get; internal set; } + /// /// The path of this property group /// public string Path { get; internal set; } - /// - /// The parent group of this layer property group, set after construction - /// - public LayerPropertyGroup Parent { get; internal set; } - /// /// Gets whether this property groups properties are all initialized /// public bool PropertiesInitialized { get; private set; } - /// - /// Used to declare that this property group doesn't belong to a plugin and should use the core plugin GUID - /// - public bool IsCorePropertyGroup { get; internal set; } - - public PropertyGroupDescriptionAttribute GroupDescription { get; internal set; } - /// /// The layer brush this property group belongs to /// @@ -76,13 +75,14 @@ namespace Artemis.Core /// /// A list of all layer properties in this group /// - public ReadOnlyCollection LayerProperties => _layerProperties.AsReadOnly(); + public ReadOnlyCollection LayerProperties => _layerProperties.AsReadOnly(); /// /// A list of al child groups in this group /// public ReadOnlyCollection LayerPropertyGroups => _layerPropertyGroups.AsReadOnly(); + /// public void Dispose() { DisableProperties(); @@ -93,20 +93,16 @@ namespace Artemis.Core /// /// Recursively gets all layer properties on this group and any subgroups /// - /// - public IReadOnlyCollection GetAllLayerProperties() + public IReadOnlyCollection GetAllLayerProperties() { if (!PropertiesInitialized) - return new List(); - if (_allLayerProperties != null) - return _allLayerProperties; + return new List(); - var result = new List(LayerProperties); + var result = new List(LayerProperties); foreach (var layerPropertyGroup in LayerPropertyGroups) result.AddRange(layerPropertyGroup.GetAllLayerProperties()); - _allLayerProperties = result.AsReadOnly(); - return _allLayerProperties; + return result.AsReadOnly(); } /// @@ -116,7 +112,7 @@ namespace Artemis.Core protected abstract void PopulateDefaults(); /// - /// Called when the property group is deactivated + /// Called when the property group is aactivated /// protected abstract void EnableProperties(); @@ -125,19 +121,26 @@ namespace Artemis.Core /// protected abstract void DisableProperties(); + /// + /// Called when the property group and all its layer properties have been initialized + /// protected virtual void OnPropertyGroupInitialized() { PropertyGroupInitialized?.Invoke(this, EventArgs.Empty); } - internal void InitializeProperties(IRenderElementService renderElementService, RenderProfileElement profileElement, [NotNull] string path) + internal void Initialize(RenderProfileElement profileElement, [NotNull] string path, PluginInfo pluginInfo) { if (path == null) throw new ArgumentNullException(nameof(path)); + if (pluginInfo == null) + throw new ArgumentNullException(nameof(pluginInfo)); + // Doubt this will happen but let's make sure if (PropertiesInitialized) throw new ArtemisCoreException("Layer property group already initialized, wut"); + PluginInfo = pluginInfo; ProfileElement = profileElement; Path = path.TrimEnd('.'); @@ -146,55 +149,21 @@ namespace Artemis.Core { var propertyDescription = Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyDescriptionAttribute)); if (propertyDescription != null) - { - if (!typeof(BaseLayerProperty).IsAssignableFrom(propertyInfo.PropertyType)) - throw new ArtemisPluginException($"Layer property with PropertyDescription attribute must be of type LayerProperty at {path + propertyInfo.Name}"); - - var instance = (BaseLayerProperty) Activator.CreateInstance(propertyInfo.PropertyType, true); - if (instance == null) - throw new ArtemisPluginException($"Failed to create instance of layer property at {path + propertyInfo.Name}"); - - instance.ProfileElement = profileElement; - instance.Parent = this; - instance.PropertyDescription = (PropertyDescriptionAttribute) propertyDescription; - if (instance.PropertyDescription.DisableKeyframes) - instance.KeyframesSupported = false; - - InitializeProperty(profileElement, path + propertyInfo.Name, instance); - - propertyInfo.SetValue(this, instance); - _layerProperties.Add(instance); - } + InitializeProperty(propertyInfo, (PropertyDescriptionAttribute) propertyDescription); else { var propertyGroupDescription = Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyGroupDescriptionAttribute)); if (propertyGroupDescription != null) - { - if (!typeof(LayerPropertyGroup).IsAssignableFrom(propertyInfo.PropertyType)) - throw new ArtemisPluginException("Layer property with PropertyGroupDescription attribute must be of type LayerPropertyGroup"); - - var instance = (LayerPropertyGroup) Activator.CreateInstance(propertyInfo.PropertyType); - if (instance == null) - throw new ArtemisPluginException($"Failed to create instance of layer property group at {path + propertyInfo.Name}"); - - instance.Parent = this; - instance.GroupDescription = (PropertyGroupDescriptionAttribute) propertyGroupDescription; - instance.LayerBrush = LayerBrush; - instance.LayerEffect = LayerEffect; - instance.InitializeProperties(renderElementService, profileElement, $"{path}{propertyInfo.Name}."); - - propertyInfo.SetValue(this, instance); - _layerPropertyGroups.Add(instance); - } + InitializeChildGroup(propertyInfo, (PropertyGroupDescriptionAttribute) propertyGroupDescription); } } // Request the property group to populate defaults PopulateDefaults(); - // Apply the newly populated defaults - foreach (var layerProperty in _layerProperties.Where(p => !p.IsLoadedFromStorage)) - layerProperty.ApplyDefaultValue(); + // Load the layer properties after defaults have been applied + foreach (var layerProperty in _layerProperties) + layerProperty.Load(); EnableProperties(); PropertiesInitialized = true; @@ -206,25 +175,11 @@ namespace Artemis.Core if (!PropertiesInitialized) return; - // Get all properties with a PropertyDescriptionAttribute - foreach (var propertyInfo in GetType().GetProperties()) - { - var propertyDescription = Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyDescriptionAttribute)); - if (propertyDescription != null) - { - var layerProperty = (BaseLayerProperty) propertyInfo.GetValue(this); - layerProperty.ApplyToEntity(); - } - else - { - var propertyGroupDescription = Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyGroupDescriptionAttribute)); - if (propertyGroupDescription != null) - { - var layerPropertyGroup = (LayerPropertyGroup) propertyInfo.GetValue(this); - layerPropertyGroup.ApplyToEntity(); - } - } - } + foreach (var layerProperty in LayerProperties) + layerProperty.Save(); + + foreach (var layerPropertyGroup in LayerPropertyGroups) + layerPropertyGroup.ApplyToEntity(); } internal void Update(double deltaTime) @@ -234,34 +189,57 @@ namespace Artemis.Core OnPropertyGroupUpdating(new LayerPropertyGroupUpdatingEventArgs(deltaTime)); } - private void InitializeProperty(RenderProfileElement profileElement, string path, BaseLayerProperty instance) + private void InitializeProperty(PropertyInfo propertyInfo, PropertyDescriptionAttribute propertyDescription) { - Guid pluginGuid; - if (IsCorePropertyGroup || instance.IsCoreProperty) - pluginGuid = Constants.CorePluginInfo.Guid; - else if (instance.Parent.LayerBrush != null) - pluginGuid = instance.Parent.LayerBrush.PluginInfo.Guid; - else - pluginGuid = instance.Parent.LayerEffect.PluginInfo.Guid; + var path = Path + "."; - var entity = profileElement.RenderElementEntity.PropertyEntities.FirstOrDefault(p => p.PluginGuid == pluginGuid && p.Path == path); - var fromStorage = true; + if (!typeof(ILayerProperty).IsAssignableFrom(propertyInfo.PropertyType)) + throw new ArtemisPluginException($"Layer property with PropertyDescription attribute must be of type LayerProperty at {path + propertyInfo.Name}"); + + var instance = (ILayerProperty) Activator.CreateInstance(propertyInfo.PropertyType, true); + if (instance == null) + throw new ArtemisPluginException($"Failed to create instance of layer property at {path + propertyInfo.Name}"); + + var entity = GetPropertyEntity(ProfileElement, path + propertyInfo.Name, out var fromStorage); + instance.Initialize(ProfileElement, this, entity, fromStorage, propertyDescription); + propertyInfo.SetValue(this, instance); + _layerProperties.Add(instance); + } + + private void InitializeChildGroup(PropertyInfo propertyInfo, PropertyGroupDescriptionAttribute propertyGroupDescription) + { + var path = Path + "."; + + if (!typeof(LayerPropertyGroup).IsAssignableFrom(propertyInfo.PropertyType)) + throw new ArtemisPluginException("Layer property with PropertyGroupDescription attribute must be of type LayerPropertyGroup"); + + var instance = (LayerPropertyGroup) Activator.CreateInstance(propertyInfo.PropertyType); + if (instance == null) + throw new ArtemisPluginException($"Failed to create instance of layer property group at {path + propertyInfo.Name}"); + + instance.Parent = this; + instance.GroupDescription = propertyGroupDescription; + instance.LayerBrush = LayerBrush; + instance.LayerEffect = LayerEffect; + instance.Initialize(ProfileElement, $"{path}{propertyInfo.Name}.", PluginInfo); + + propertyInfo.SetValue(this, instance); + _layerPropertyGroups.Add(instance); + } + + private PropertyEntity GetPropertyEntity(RenderProfileElement profileElement, string path, out bool fromStorage) + { + var entity = profileElement.RenderElementEntity.PropertyEntities.FirstOrDefault(p => p.PluginGuid == PluginInfo.Guid && p.Path == path); + fromStorage = entity != null; if (entity == null) { - fromStorage = false; - entity = new PropertyEntity {PluginGuid = pluginGuid, Path = path}; + entity = new PropertyEntity {PluginGuid = PluginInfo.Guid, Path = path}; profileElement.RenderElementEntity.PropertyEntities.Add(entity); } - instance.ApplyToLayerProperty(entity, this, fromStorage); - instance.BaseValueChanged += InstanceOnBaseValueChanged; + return entity; } - - private void InstanceOnBaseValueChanged(object sender, EventArgs e) - { - OnLayerPropertyBaseValueChanged(new LayerPropertyEventArgs((BaseLayerProperty) sender)); - } - + #region Events internal event EventHandler PropertyGroupUpdating; @@ -282,17 +260,17 @@ namespace Artemis.Core /// public event EventHandler VisibilityChanged; - protected virtual void OnPropertyGroupUpdating(LayerPropertyGroupUpdatingEventArgs e) + internal virtual void OnPropertyGroupUpdating(LayerPropertyGroupUpdatingEventArgs e) { PropertyGroupUpdating?.Invoke(this, e); } - protected virtual void OnVisibilityChanged() + internal virtual void OnVisibilityChanged() { VisibilityChanged?.Invoke(this, EventArgs.Empty); } - protected virtual void OnLayerPropertyBaseValueChanged(LayerPropertyEventArgs e) + internal virtual void OnLayerPropertyBaseValueChanged(LayerPropertyEventArgs e) { LayerPropertyBaseValueChanged?.Invoke(this, e); } diff --git a/src/Artemis.Core/Plugins/LayerBrushes/Internal/PropertiesLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/Internal/PropertiesLayerBrush.cs index 52a2de9d6..900f8bd9c 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/Internal/PropertiesLayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrushes/Internal/PropertiesLayerBrush.cs @@ -1,5 +1,4 @@ using System; -using Artemis.Core.Services; namespace Artemis.Core.LayerBrushes { @@ -33,11 +32,11 @@ namespace Artemis.Core.LayerBrushes internal set => _properties = value; } - internal void InitializeProperties(IRenderElementService renderElementService) + internal void InitializeProperties() { Properties = Activator.CreateInstance(); Properties.LayerBrush = this; - Properties.InitializeProperties(renderElementService, Layer, "LayerBrush."); + Properties.Initialize(Layer, "LayerBrush.", PluginInfo); PropertiesInitialized = true; EnableLayerBrush(); diff --git a/src/Artemis.Core/Plugins/LayerEffects/LayerEffect.cs b/src/Artemis.Core/Plugins/LayerEffects/LayerEffect.cs index 57db9e522..03b43e8ee 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/LayerEffect.cs +++ b/src/Artemis.Core/Plugins/LayerEffects/LayerEffect.cs @@ -30,11 +30,11 @@ namespace Artemis.Core.LayerEffects internal set => _properties = value; } - internal void InitializeProperties(IRenderElementService renderElementService) + internal void InitializeProperties() { Properties = Activator.CreateInstance(); Properties.LayerEffect = this; - Properties.InitializeProperties(renderElementService, ProfileElement, PropertyRootPath); + Properties.Initialize(ProfileElement, PropertyRootPath, PluginInfo); PropertiesInitialized = true; EnableLayerEffect(); diff --git a/src/Artemis.Core/Services/DataBindingService.cs b/src/Artemis.Core/Services/DataBindingService.cs index 199408fa2..3af46569f 100644 --- a/src/Artemis.Core/Services/DataBindingService.cs +++ b/src/Artemis.Core/Services/DataBindingService.cs @@ -91,14 +91,9 @@ namespace Artemis.Core.Services return RegisteredDataBindingModifierTypes.FirstOrDefault(o => o.PluginInfo.Guid == modifierTypePluginGuid && o.GetType().Name == modifierType); } - public void LogModifierDeserializationFailure(DataBindingModifier dataBindingModifier, JsonSerializationException exception) + public void LogModifierDeserializationFailure(string modifierName, JsonSerializationException exception) { - _logger.Warning( - exception, - "Failed to deserialize static parameter for operator {order}. {operatorType}", - dataBindingModifier.Entity.Order, - dataBindingModifier.Entity.ModifierType - ); + _logger.Warning(exception, "Failed to deserialize static parameter for modifier {modifierName}", modifierName); } private void RegisterBuiltInModifiers() diff --git a/src/Artemis.Core/Services/Interfaces/IDataBindingService.cs b/src/Artemis.Core/Services/Interfaces/IDataBindingService.cs index 8d9201383..1ed4bbeb2 100644 --- a/src/Artemis.Core/Services/Interfaces/IDataBindingService.cs +++ b/src/Artemis.Core/Services/Interfaces/IDataBindingService.cs @@ -41,8 +41,8 @@ namespace Artemis.Core.Services /// /// Logs a modifier deserialization failure /// - /// The modifier that failed to deserialize + /// The modifier that failed to deserialize /// The JSON exception that occurred - void LogModifierDeserializationFailure(DataBindingModifier dataBindingModifier, JsonSerializationException exception); + void LogModifierDeserializationFailure(string modifierName, JsonSerializationException exception); } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/RenderElementService.cs b/src/Artemis.Core/Services/RenderElementService.cs index 6b26c94de..9178e6c94 100644 --- a/src/Artemis.Core/Services/RenderElementService.cs +++ b/src/Artemis.Core/Services/RenderElementService.cs @@ -30,8 +30,8 @@ namespace Artemis.Core.Services parent.AddChild(layer); // Layers have two hardcoded property groups, instantiate them - layer.General.InitializeProperties(this, layer, "General."); - layer.Transform.InitializeProperties(this, layer, "Transform."); + layer.General.Initialize(layer, "General.", Constants.CorePluginInfo); + layer.Transform.Initialize(layer, "Transform.", Constants.CorePluginInfo); // With the properties loaded, the layer brush and effect can be instantiated InstantiateLayerBrush(layer); diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index f66031fca..6b50447c4 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -278,9 +278,9 @@ namespace Artemis.Core.Services foreach (var layer in profile.GetAllLayers()) { if (!layer.General.PropertiesInitialized) - layer.General.InitializeProperties(_renderElementService, layer, "General."); + layer.General.Initialize(layer, "General.", Constants.CorePluginInfo); if (!layer.Transform.PropertiesInitialized) - layer.Transform.InitializeProperties(_renderElementService, layer, "Transform."); + layer.Transform.Initialize(layer, "Transform.", Constants.CorePluginInfo); } }