diff --git a/src/Artemis.Core/DefaultTypes/DataBindings/Converters/FloatDataBindingConverter.cs b/src/Artemis.Core/DefaultTypes/DataBindings/Converters/FloatDataBindingConverter.cs index b076134bb..75818ac95 100644 --- a/src/Artemis.Core/DefaultTypes/DataBindings/Converters/FloatDataBindingConverter.cs +++ b/src/Artemis.Core/DefaultTypes/DataBindings/Converters/FloatDataBindingConverter.cs @@ -36,9 +36,6 @@ namespace Artemis.Core /// public override void ApplyValue(float value) { - if (ValueTypeSetExpression == null) - return; - if (DataBinding!.LayerProperty.PropertyDescription.MaxInputValue is float max) value = Math.Min(value, max); if (DataBinding!.LayerProperty.PropertyDescription.MinInputValue is float min) diff --git a/src/Artemis.Core/DefaultTypes/Properties/BoolLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/BoolLayerProperty.cs index bfeb477f4..567ef6002 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/BoolLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/BoolLayerProperty.cs @@ -6,7 +6,7 @@ internal BoolLayerProperty() { KeyframesSupported = false; - RegisterDataBindingProperty(b => b, new GeneralDataBindingConverter()); + RegisterDataBindingProperty(value => value, (_, newValue) => CurrentValue = newValue, new GeneralDataBindingConverter(), "Value"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs index 449461bec..39f02c655 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs @@ -1,4 +1,6 @@ -namespace Artemis.Core +using SkiaSharp; + +namespace Artemis.Core { /// public class ColorGradientLayerProperty : LayerProperty @@ -6,12 +8,33 @@ internal ColorGradientLayerProperty() { KeyframesSupported = false; - DataBindingsSupported = false; + DataBindingsSupported = true; DefaultValue = new ColorGradient(); - + CurrentValueSet += OnCurrentValueSet; } + private void CreateDataBindingRegistrations() + { + ClearDataBindingProperties(); + if (CurrentValue == null) + return; + + for (int index = 0; index < CurrentValue.Stops.Count; index++) + { + int stopIndex = index; + DataBindingRegistration registerDataBindingProperty = RegisterDataBindingProperty( + gradient => gradient.Stops[stopIndex].Color, + (gradient, value) => gradient.Stops[stopIndex].Color = value, + new ColorStopDataBindingConverter(), + $"Color #{stopIndex + 1}" + ); + + registerDataBindingProperty.DisplayName = $"Color #{stopIndex + 1}"; + } + } + + /// /// Implicitly converts an to a /// @@ -31,6 +54,8 @@ // Don't allow color gradients to be null if (BaseValue == null) BaseValue = DefaultValue ?? new ColorGradient(); + + CreateDataBindingRegistrations(); } #region Overrides of LayerProperty @@ -41,10 +66,31 @@ // Don't allow color gradients to be null if (BaseValue == null) BaseValue = DefaultValue ?? new ColorGradient(); - + base.OnInitialize(); } #endregion } + + internal class ColorStopDataBindingConverter : DataBindingConverter + { + public ColorStopDataBindingConverter() + { + SupportsInterpolate = true; + SupportsSum = true; + } + + /// + public override SKColor Sum(SKColor a, SKColor b) + { + return a.Sum(b); + } + + /// + public override SKColor Interpolate(SKColor a, SKColor b, double progress) + { + return a.Interpolate(b, (float) progress); + } + } } \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Properties/FloatLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/FloatLayerProperty.cs index 55221deaf..589244657 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/FloatLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/FloatLayerProperty.cs @@ -5,7 +5,7 @@ { internal FloatLayerProperty() { - RegisterDataBindingProperty(value => value, new FloatDataBindingConverter()); + RegisterDataBindingProperty(value => value, (_, newValue) => CurrentValue = newValue, new FloatDataBindingConverter(), "Value"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs index 4b54aca95..74baa44f9 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs @@ -5,9 +5,9 @@ { internal FloatRangeLayerProperty() { - RegisterDataBindingProperty(range => range.Start, new FloatDataBindingConverter()); - RegisterDataBindingProperty(range => range.End, new FloatDataBindingConverter()); - + RegisterDataBindingProperty(value => value.Start, (value, newValue) => value.Start = newValue, new FloatDataBindingConverter(), "Start"); + RegisterDataBindingProperty(value => value.End, (value, newValue) => value.End = newValue, new FloatDataBindingConverter(), "End"); + CurrentValueSet += OnCurrentValueSet; } diff --git a/src/Artemis.Core/DefaultTypes/Properties/IntLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/IntLayerProperty.cs index 7edd5241d..8d1fff755 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/IntLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/IntLayerProperty.cs @@ -7,7 +7,7 @@ namespace Artemis.Core { internal IntLayerProperty() { - RegisterDataBindingProperty(value => value, new IntDataBindingConverter()); + RegisterDataBindingProperty(value => value, (_, newValue) => CurrentValue = newValue, new IntDataBindingConverter(), "Value"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs index 2a9f498fe..8ca36f876 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs @@ -5,8 +5,8 @@ { internal IntRangeLayerProperty() { - RegisterDataBindingProperty(range => range.Start, new IntDataBindingConverter()); - RegisterDataBindingProperty(range => range.End, new IntDataBindingConverter()); + RegisterDataBindingProperty(value => value.Start, (value, newValue) => value.Start = newValue, new IntDataBindingConverter(), "Start"); + RegisterDataBindingProperty(value => value.End, (value, newValue) => value.End = newValue, new IntDataBindingConverter(), "End"); CurrentValueSet += OnCurrentValueSet; } diff --git a/src/Artemis.Core/DefaultTypes/Properties/SKColorLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/SKColorLayerProperty.cs index 366c4aa49..e08b9aaf6 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/SKColorLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/SKColorLayerProperty.cs @@ -7,7 +7,7 @@ namespace Artemis.Core { internal SKColorLayerProperty() { - RegisterDataBindingProperty(value => value, new SKColorDataBindingConverter()); + RegisterDataBindingProperty(value => value, (_, newValue) => CurrentValue = newValue, new SKColorDataBindingConverter(), "Value"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs index 74befb209..f32a312c0 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs @@ -7,8 +7,9 @@ namespace Artemis.Core { internal SKPointLayerProperty() { - RegisterDataBindingProperty(point => point.X, new FloatDataBindingConverter()); - RegisterDataBindingProperty(point => point.Y, new FloatDataBindingConverter()); + RegisterDataBindingProperty(value => value.X, (value, newValue) => value.X = newValue, new FloatDataBindingConverter(), "X"); + RegisterDataBindingProperty(value => value.Y, (value, newValue) => value.Y = newValue, new FloatDataBindingConverter(), "Y"); + } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/SKSizeLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/SKSizeLayerProperty.cs index 0d98286b6..d018b4fb5 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/SKSizeLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/SKSizeLayerProperty.cs @@ -7,8 +7,8 @@ namespace Artemis.Core { internal SKSizeLayerProperty() { - RegisterDataBindingProperty(size => size.Height, new FloatDataBindingConverter()); - RegisterDataBindingProperty(size => size.Width, new FloatDataBindingConverter()); + RegisterDataBindingProperty(value => value.Height, (value, newValue) => value.Height = newValue, new FloatDataBindingConverter(), "Height"); + RegisterDataBindingProperty(value => value.Width, (value, newValue) => value.Width = newValue, new FloatDataBindingConverter(), "Width"); } /// diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs index 18c18894c..44370e498 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs @@ -97,7 +97,7 @@ namespace Artemis.Core /// public Type? GetTargetType() { - return Registration?.PropertyExpression.ReturnType; + return Registration?.Getter.Method.ReturnType; } private void ResetEasing(TProperty value) @@ -293,7 +293,7 @@ namespace Artemis.Core // Don't save an invalid state if (Registration != null) - Entity.TargetExpression = Registration.PropertyExpression.ToString(); + Entity.TargetExpression = Registration.Getter.ToString(); Entity.EasingTime = EasingTime; Entity.EasingFunction = (int) EasingFunction; diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs index fe4254c22..a7ec330ed 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs @@ -1,6 +1,4 @@ using System; -using System.Linq.Expressions; -using System.Reflection; namespace Artemis.Core { @@ -10,21 +8,6 @@ namespace Artemis.Core /// public abstract class DataBindingConverter : IDataBindingConverter { - /// - /// Gets a dynamically compiled getter pointing to the data bound property - /// - public Func? GetExpression { get; private set; } - - /// - /// Gets a dynamically compiled setter pointing to the data bound property used for value types - /// - public Action? ValueTypeSetExpression { get; private set; } - - /// - /// Gets a dynamically compiled setter pointing to the data bound property used for reference types - /// - public Action? ReferenceTypeSetExpression { get; private set; } - /// /// Gets the data binding this converter is applied to /// @@ -40,9 +23,6 @@ namespace Artemis.Core /// public bool SupportsInterpolate { get; protected set; } - /// - public Type SupportedType => typeof(TProperty); - /// /// Returns the sum of and /// @@ -65,12 +45,9 @@ namespace Artemis.Core /// public virtual void ApplyValue(TProperty value) { - if (DataBinding == null) + if (DataBinding?.Registration == null) throw new ArtemisCoreException("Data binding converter is not yet initialized"); - if (ReferenceTypeSetExpression != null) - ReferenceTypeSetExpression(DataBinding.LayerProperty.CurrentValue, value); - else if (ValueTypeSetExpression != null) - ValueTypeSetExpression(value); + DataBinding.Registration.Setter(DataBinding.LayerProperty.CurrentValue, value); } /// @@ -78,9 +55,9 @@ namespace Artemis.Core /// public virtual TProperty GetValue() { - if (DataBinding == null || GetExpression == null) + if (DataBinding?.Registration == null) throw new ArtemisCoreException("Data binding converter is not yet initialized"); - return GetExpression(DataBinding.LayerProperty.CurrentValue); + return DataBinding.Registration.Getter(DataBinding.LayerProperty.CurrentValue); } /// @@ -104,83 +81,10 @@ namespace Artemis.Core throw new ArtemisCoreException("Cannot initialize a data binding converter for a data binding without a registration"); DataBinding = dataBinding; - GetExpression = dataBinding.Registration.PropertyExpression.Compile(); - CreateSetExpression(); - OnInitialized(); } - private void CreateSetExpression() - { - // If the registration does not point towards a member of LayerProperty.CurrentValue, assign directly to LayerProperty.CurrentValue - if (DataBinding!.Registration?.Member == null) - { - CreateSetCurrentValueExpression(); - return; - } - - // Ensure the member of LayerProperty.CurrentValue has a setter - MethodInfo? setterMethod = null; - if (DataBinding.Registration.Member is PropertyInfo propertyInfo) - setterMethod = propertyInfo.GetSetMethod(); - // If there is no setter, the built-in data binding cannot do its job, stay null - if (setterMethod == null) - return; - - // If LayerProperty.CurrentValue is a value type, assign it directly to LayerProperty.CurrentValue after applying the changes - if (typeof(TLayerProperty).IsValueType) - CreateSetValueTypeExpression(); - // If it is a reference type it can safely be updated by its reference - else - CreateSetReferenceTypeExpression(); - } - - private void CreateSetReferenceTypeExpression() - { - if (DataBinding!.Registration?.Member == null) - throw new ArtemisCoreException("Cannot create value setter for data binding without a registration"); - - ParameterExpression propertyValue = Expression.Parameter(typeof(TProperty), "propertyValue"); - ParameterExpression parameter = Expression.Parameter(typeof(TLayerProperty), "currentValue"); - MemberExpression memberAccess = Expression.MakeMemberAccess(parameter, DataBinding.Registration.Member); - BinaryExpression assignment = Expression.Assign(memberAccess, propertyValue); - Expression> referenceTypeLambda = Expression.Lambda>(assignment, parameter, propertyValue); - - ReferenceTypeSetExpression = referenceTypeLambda.Compile(); - } - - private void CreateSetValueTypeExpression() - { - if (DataBinding!.Registration?.Member == null) - throw new ArtemisCoreException("Cannot create value setter for data binding without a registration"); - - ParameterExpression propertyValue = Expression.Parameter(typeof(TProperty), "propertyValue"); - ParameterExpression variableCurrent = Expression.Variable(typeof(TLayerProperty), "current"); - ConstantExpression layerProperty = Expression.Constant(DataBinding.LayerProperty); - MemberExpression layerPropertyMemberAccess = Expression.MakeMemberAccess(layerProperty, - DataBinding.LayerProperty.GetType().GetMember(nameof(DataBinding.LayerProperty.CurrentValue))[0]); - - BlockExpression body = Expression.Block( - new[] {variableCurrent}, - Expression.Assign(variableCurrent, layerPropertyMemberAccess), - Expression.Assign(Expression.MakeMemberAccess(variableCurrent, DataBinding.Registration.Member), propertyValue), - Expression.Assign(layerPropertyMemberAccess, variableCurrent) - ); - - Expression> valueTypeLambda = Expression.Lambda>(body, propertyValue); - ValueTypeSetExpression = valueTypeLambda.Compile(); - } - - private void CreateSetCurrentValueExpression() - { - ParameterExpression propertyValue = Expression.Parameter(typeof(TProperty), "propertyValue"); - ConstantExpression layerProperty = Expression.Constant(DataBinding!.LayerProperty); - MemberExpression layerPropertyMemberAccess = Expression.MakeMemberAccess(layerProperty, - DataBinding.LayerProperty.GetType().GetMember(nameof(DataBinding.LayerProperty.CurrentValue))[0]); - - BinaryExpression body = Expression.Assign(layerPropertyMemberAccess, propertyValue); - Expression> lambda = Expression.Lambda>(body, propertyValue); - ValueTypeSetExpression = lambda.Compile(); - } + /// + public Type SupportedType => typeof(TProperty); } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs index 00346e68b..e43c28550 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs @@ -1,7 +1,5 @@ using System; using System.Linq; -using System.Linq.Expressions; -using System.Reflection; using Artemis.Storage.Entities.Profile.DataBindings; namespace Artemis.Core @@ -9,16 +7,16 @@ namespace Artemis.Core /// public class DataBindingRegistration : IDataBindingRegistration { - internal DataBindingRegistration(LayerProperty layerProperty, - DataBindingConverter converter, - Expression> propertyExpression) + internal DataBindingRegistration(LayerProperty layerProperty, DataBindingConverter converter, + Func getter, + Action setter, + string displayName) { LayerProperty = layerProperty ?? throw new ArgumentNullException(nameof(layerProperty)); Converter = converter ?? throw new ArgumentNullException(nameof(converter)); - PropertyExpression = propertyExpression ?? throw new ArgumentNullException(nameof(propertyExpression)); - - if (propertyExpression.Body is MemberExpression memberExpression) - Member = memberExpression.Member; + Getter = getter ?? throw new ArgumentNullException(nameof(getter)); + Setter = setter ?? throw new ArgumentNullException(nameof(setter)); + DisplayName = displayName ?? throw new ArgumentNullException(nameof(displayName)); } /// @@ -32,15 +30,19 @@ namespace Artemis.Core public DataBindingConverter Converter { get; } /// - /// Gets the expression that that accesses the property + /// Gets the function to call to get the value of the property /// - public Expression> PropertyExpression { get; } + public Func Getter { get; } /// - /// Gets the member the targets - /// if the is not a member expression + /// Gets the action to call to set the value of the property /// - public MemberInfo? Member { get; } + public Action Setter { get; } + + /// + /// Gets or sets the display name of the data binding registration + /// + public string DisplayName { get; set; } /// /// Gets the data binding created using this registration @@ -59,7 +61,7 @@ namespace Artemis.Core if (DataBinding != null) return DataBinding; - DataBindingEntity? dataBinding = LayerProperty.Entity.DataBindingEntities.FirstOrDefault(e => e.TargetExpression == PropertyExpression.ToString()); + DataBindingEntity? dataBinding = LayerProperty.Entity.DataBindingEntities.FirstOrDefault(e => e.TargetExpression == Getter.ToString()); if (dataBinding == null) return null; diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs index bb9760a91..b184f6e24 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs @@ -393,7 +393,7 @@ namespace Artemis.Core throw new ObjectDisposedException("LayerProperty"); IDataBindingRegistration? match = _dataBindingRegistrations.FirstOrDefault(r => r is DataBindingRegistration registration && - registration.PropertyExpression.ToString() == expression); + registration.Getter.ToString() == expression); return (DataBindingRegistration?) match; } @@ -410,21 +410,30 @@ namespace Artemis.Core /// Registers a data binding property so that is available to the data binding system /// /// The type of the layer property - /// The expression pointing to the value to register + /// The function to call to get the value of the property + /// The action to call to set the value of the property /// The converter to use while applying the data binding - public void RegisterDataBindingProperty(Expression> propertyExpression, DataBindingConverter converter) + /// The display name of the data binding property + public DataBindingRegistration RegisterDataBindingProperty(Func getter, Action setter, DataBindingConverter converter, + string displayName) { if (_disposed) throw new ObjectDisposedException("LayerProperty"); - if (propertyExpression.Body.NodeType != ExpressionType.MemberAccess && propertyExpression.Body.NodeType != ExpressionType.Parameter) - throw new ArtemisCoreException("Provided expression is invalid, it must be 'value => value' or 'value => value.Property'"); + DataBindingRegistration registration = new(this, converter, getter, setter, displayName); + _dataBindingRegistrations.Add(registration); + return registration; + } - if (converter.SupportedType != propertyExpression.ReturnType) - throw new ArtemisCoreException($"Cannot register data binding property for property {PropertyDescription.Name} " + - "because the provided converter does not support the property's type"); + /// + /// Removes all data binding properties so they are no longer available to the data binding system + /// + public void ClearDataBindingProperties() + { + if (_disposed) + throw new ObjectDisposedException("LayerProperty"); - _dataBindingRegistrations.Add(new DataBindingRegistration(this, converter, propertyExpression)); + _dataBindingRegistrations.Clear(); } /// diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs index 0ba67c4ab..105af6c65 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs @@ -38,11 +38,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings _profileEditorService = profileEditorService; _dataBindingsVmFactory = dataBindingsVmFactory; - if (Registration.Member != null) - DisplayName = Registration.Member.Name.ToUpper(); - else - DisplayName = Registration.LayerProperty.PropertyDescription.Name.ToUpper(); - + DisplayName = Registration.DisplayName.ToUpper(); AlwaysApplyDataBindings = settingsService.GetSetting("ProfileEditor.AlwaysApplyDataBindings", true); DataBindingModes = new BindableCollection(EnumUtilities.GetAllValuesAndDescriptions(typeof(DataBindingModeType))); EasingViewModels = new BindableCollection(); diff --git a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs index 071884dcd..f3620ba15 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs @@ -302,7 +302,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization TimeSpan delta = DateTime.Now - _lastUpdate; _lastUpdate = DateTime.Now; - if (!AlwaysApplyDataBindings.Value || _profileEditorService.SelectedProfile == null || _profileEditorService.Playing) + if (!AlwaysApplyDataBindings.Value || _profileEditorService.SelectedProfile == null) return; foreach (IDataBindingRegistration dataBindingRegistration in _profileEditorService.SelectedProfile.GetAllFolders()