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..99a7b614a 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(() => CurrentValue, value => CurrentValue = value, new GeneralDataBindingConverter(), "Value"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs index 449461bec..387c2d9ef 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs @@ -1,17 +1,43 @@ -namespace Artemis.Core +using System.ComponentModel; +using SkiaSharp; + +namespace Artemis.Core { /// public class ColorGradientLayerProperty : LayerProperty { + private ColorGradient? _subscribedGradient; + 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; + + void Setter(SKColor value) + { + CurrentValue.Stops[stopIndex].Color = value; + CurrentValue.OnColorValuesUpdated(); + } + + RegisterDataBindingProperty(() => CurrentValue.Stops[stopIndex].Color, Setter, new ColorStopDataBindingConverter(), $"Color #{stopIndex + 1}"); + } + } + + /// /// Implicitly converts an to a /// @@ -31,6 +57,22 @@ // Don't allow color gradients to be null if (BaseValue == null) BaseValue = DefaultValue ?? new ColorGradient(); + + if (_subscribedGradient != BaseValue) + { + if (_subscribedGradient != null) + _subscribedGradient.PropertyChanged -= SubscribedGradientOnPropertyChanged; + _subscribedGradient = BaseValue; + _subscribedGradient.PropertyChanged += SubscribedGradientOnPropertyChanged; + } + + CreateDataBindingRegistrations(); + } + + private void SubscribedGradientOnPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (CurrentValue.Stops.Count != GetAllDataBindingRegistrations().Count) + CreateDataBindingRegistrations(); } #region Overrides of LayerProperty @@ -41,10 +83,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..cb1a6eae8 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(() => CurrentValue, value => CurrentValue = value, new FloatDataBindingConverter(), "Value"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs index 4b54aca95..298e824f3 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs @@ -5,8 +5,8 @@ { internal FloatRangeLayerProperty() { - RegisterDataBindingProperty(range => range.Start, new FloatDataBindingConverter()); - RegisterDataBindingProperty(range => range.End, new FloatDataBindingConverter()); + RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue.Start = value, new FloatDataBindingConverter(), "Start"); + RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, 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..f69a1bdbc 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(() => CurrentValue, value => CurrentValue = value, new IntDataBindingConverter(), "Value"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs index 2a9f498fe..474fa33e1 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(() => CurrentValue.Start, value => CurrentValue.Start = value, new IntDataBindingConverter(), "Start"); + RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, 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..46370568a 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(() => CurrentValue, value => CurrentValue = value, new SKColorDataBindingConverter(), "Value"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs index 74befb209..3cd654ad9 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/SKPointLayerProperty.cs @@ -7,8 +7,8 @@ namespace Artemis.Core { internal SKPointLayerProperty() { - RegisterDataBindingProperty(point => point.X, new FloatDataBindingConverter()); - RegisterDataBindingProperty(point => point.Y, new FloatDataBindingConverter()); + RegisterDataBindingProperty(() => CurrentValue.X, value => CurrentValue = new SKPoint(value, CurrentValue.Y), new FloatDataBindingConverter(), "X"); + RegisterDataBindingProperty(() => CurrentValue.Y, value => CurrentValue = new SKPoint(CurrentValue.X, value), new FloatDataBindingConverter(), "Y"); } /// diff --git a/src/Artemis.Core/DefaultTypes/Properties/SKSizeLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/SKSizeLayerProperty.cs index 0d98286b6..3274c26f9 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(() => CurrentValue.Width, (value) => CurrentValue = new SKSize(value, CurrentValue.Height), new FloatDataBindingConverter(), "Width"); + RegisterDataBindingProperty(() => CurrentValue.Height, (value) => CurrentValue = new SKSize(CurrentValue.Width, value), new FloatDataBindingConverter(), "Height"); } /// diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs index 18c18894c..4bfbacc48 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) @@ -272,7 +272,7 @@ namespace Artemis.Core throw new ObjectDisposedException("DataBinding"); // General - DataBindingRegistration? registration = LayerProperty.GetDataBindingRegistration(Entity.TargetExpression); + DataBindingRegistration? registration = LayerProperty.GetDataBindingRegistration(Entity.Identifier); if (registration != null) ApplyRegistration(registration); @@ -293,7 +293,7 @@ namespace Artemis.Core // Don't save an invalid state if (Registration != null) - Entity.TargetExpression = Registration.PropertyExpression.ToString(); + Entity.Identifier = Registration.DisplayName; 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..2b3d7eb5a 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(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(); } /// @@ -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..78e233fe1 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,14 @@ 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 +28,17 @@ 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; } + + /// + public string DisplayName { get; } /// /// Gets the data binding created using this registration @@ -59,7 +57,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.Identifier == DisplayName); if (dataBinding == null) return null; diff --git a/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingRegistration.cs b/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingRegistration.cs index fb6220951..8e1de1f0a 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingRegistration.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingRegistration.cs @@ -5,6 +5,11 @@ /// public interface IDataBindingRegistration { + /// + /// Gets or sets the display name of the data binding registration + /// + string DisplayName { get; } + /// /// Returns the data binding applied using this registration /// diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs index 33153f4ef..c42b1f74c 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs @@ -97,6 +97,16 @@ namespace Artemis.Core /// public event EventHandler? KeyframeRemoved; + /// + /// Occurs when a data binding property has been added + /// + public event EventHandler? DataBindingPropertyRegistered; + + /// + /// Occurs when all data binding properties have been removed + /// + public event EventHandler? DataBindingPropertiesCleared; + /// /// Occurs when a data binding has been enabled /// diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs index bb9760a91..61f38c428 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs @@ -379,21 +379,16 @@ namespace Artemis.Core public bool HasDataBinding => GetAllDataBindingRegistrations().Any(r => r.GetDataBinding() != null); /// - /// Gets a data binding registration by the expression used to register it + /// Gets a data binding registration by the display name used to register it /// Note: The expression must exactly match the one used to register the data binding /// - public DataBindingRegistration? GetDataBindingRegistration(Expression> propertyExpression) - { - return GetDataBindingRegistration(propertyExpression.ToString()); - } - - internal DataBindingRegistration? GetDataBindingRegistration(string expression) + public DataBindingRegistration? GetDataBindingRegistration(string identifier) { if (_disposed) throw new ObjectDisposedException("LayerProperty"); IDataBindingRegistration? match = _dataBindingRegistrations.FirstOrDefault(r => r is DataBindingRegistration registration && - registration.PropertyExpression.ToString() == expression); + registration.DisplayName == identifier); return (DataBindingRegistration?) match; } @@ -410,21 +405,35 @@ 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 (_dataBindingRegistrations.Any(d => d.DisplayName == displayName)) + throw new ArtemisCoreException($"A databinding property named '{displayName}' is already registered."); + + DataBindingRegistration registration = new(this, converter, getter, setter, displayName); + _dataBindingRegistrations.Add(registration); + + OnDataBindingPropertyRegistered(); + return registration; + } + + /// + /// 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"); - 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'"); - - 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"); - - _dataBindingRegistrations.Add(new DataBindingRegistration(this, converter, propertyExpression)); + _dataBindingRegistrations.Clear(); + OnDataBindingPropertiesCleared(); } /// @@ -661,6 +670,12 @@ namespace Artemis.Core /// public event EventHandler? KeyframeRemoved; + /// + public event EventHandler? DataBindingPropertyRegistered; + + /// + public event EventHandler? DataBindingPropertiesCleared; + /// public event EventHandler? DataBindingEnabled; @@ -716,6 +731,22 @@ namespace Artemis.Core KeyframeRemoved?.Invoke(this, new LayerPropertyEventArgs(this)); } + /// + /// Invokes the event + /// + protected virtual void OnDataBindingPropertyRegistered() + { + DataBindingPropertyRegistered?.Invoke(this, new LayerPropertyEventArgs(this)); + } + + /// + /// Invokes the event + /// + protected virtual void OnDataBindingPropertiesCleared() + { + DataBindingPropertiesCleared?.Invoke(this, new LayerPropertyEventArgs(this)); + } + /// /// Invokes the event /// diff --git a/src/Artemis.Core/Plugins/PluginFeature.cs b/src/Artemis.Core/Plugins/PluginFeature.cs index 1eadf61aa..f2fb01fad 100644 --- a/src/Artemis.Core/Plugins/PluginFeature.cs +++ b/src/Artemis.Core/Plugins/PluginFeature.cs @@ -73,10 +73,10 @@ namespace Artemis.Core if (!enable) { - IsEnabled = false; - // Even if disable failed, still leave it in a disabled state to avoid more issues InternalDisable(); + IsEnabled = false; + OnDisabled(); return; } @@ -146,7 +146,7 @@ namespace Artemis.Core { if (disposing) { - Disable(); + InternalDisable(); } } diff --git a/src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingEntity.cs b/src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingEntity.cs index 0a0dc030c..6dd27b85a 100644 --- a/src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingEntity.cs @@ -4,7 +4,7 @@ namespace Artemis.Storage.Entities.Profile.DataBindings { public class DataBindingEntity { - public string TargetExpression { get; set; } + public string Identifier { get; set; } public TimeSpan EasingTime { get; set; } public int EasingFunction { get; set; } diff --git a/src/Artemis.Storage/Migrations/M10BetterDataBindings.cs b/src/Artemis.Storage/Migrations/M10BetterDataBindings.cs new file mode 100644 index 000000000..4b7daf20f --- /dev/null +++ b/src/Artemis.Storage/Migrations/M10BetterDataBindings.cs @@ -0,0 +1,55 @@ +using Artemis.Storage.Migrations.Interfaces; +using LiteDB; + +namespace Artemis.Storage.Migrations +{ + public class M10BetterDataBindings : IStorageMigration + { + private void Migrate(BsonValue bsonValue) + { + if (!bsonValue.IsDocument || !bsonValue.AsDocument.TryGetValue("PropertyEntities", out BsonValue propertyEntities)) + return; + + foreach (BsonValue propertyEntity in propertyEntities.AsArray) + { + if (!propertyEntity.AsDocument.TryGetValue("DataBindingEntities", out BsonValue dataBindingEntities)) + continue; + foreach (BsonValue dataBindingEntity in dataBindingEntities.AsArray) + { + if (!dataBindingEntity.AsDocument.TryGetValue("TargetExpression", out BsonValue targetExpression)) + continue; + string value = targetExpression.AsString; + if (value == "value => value" || value == "b => b") + { + dataBindingEntity.AsDocument["Identifier"] = "Value"; + } + else + { + string selector = value.Split("=>")[1]; + string property = selector.Split(".")[1]; + dataBindingEntity.AsDocument["Identifier"] = property; + } + + dataBindingEntity.AsDocument.Remove("TargetExpression"); + } + } + } + + public int UserVersion => 10; + + public void Apply(LiteRepository repository) + { + ILiteCollection collection = repository.Database.GetCollection("ProfileEntity"); + foreach (BsonDocument bsonDocument in collection.FindAll()) + { + foreach (BsonValue bsonLayer in bsonDocument["Layers"].AsArray) + Migrate(bsonLayer); + + foreach (BsonValue bsonLayer in bsonDocument["Folders"].AsArray) + Migrate(bsonLayer); + + collection.Update(bsonDocument); + } + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.xaml index 16f480241..ebf357f9e 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.xaml @@ -1,4 +1,4 @@ - { + private List _registrations; + public BoolPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService) : base(layerProperty, profileEditorService) { + _registrations = layerProperty.GetAllDataBindingRegistrations(); } - public bool IsEnabled => true; + public bool IsEnabled => _registrations.Any(r => r.GetDataBinding() != null); + + protected override void OnDataBindingsChanged() + { + NotifyOfPropertyChange(nameof(IsEnabled)); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.xaml index 15c14f988..aa4e288d3 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.xaml @@ -1,12 +1,12 @@ - diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs b/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs index d0b5e1b16..20409eb1c 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs @@ -7,7 +7,7 @@ using Artemis.UI.Shared; using Artemis.UI.Shared.Services; using Stylet; -namespace Artemis.UI.PropertyInput +namespace Artemis.UI.DefaultTypes.PropertyInput { public class BrushPropertyInputViewModel : PropertyInputViewModel { diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.xaml index 13ea3a9c9..ef17ba34e 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.xaml @@ -1,9 +1,8 @@ - { diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.xaml index 732d1648f..818914562 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.xaml @@ -1,4 +1,4 @@ - : PropertyInputViewModel where T : Enum { diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.xaml index 3c9bd2105..65e27826f 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.xaml @@ -1,11 +1,10 @@ - { @@ -13,7 +13,7 @@ namespace Artemis.UI.PropertyInput public FloatPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, IModelValidator validator) : base(layerProperty, profileEditorService, validator) { - _registration = layerProperty.GetDataBindingRegistration(value => value); + _registration = layerProperty.GetDataBindingRegistration("Value"); } public bool IsEnabled => _registration.DataBinding == null; diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputView.xaml index e88828009..aabe66a33 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputView.xaml @@ -1,9 +1,8 @@ - { @@ -16,8 +16,8 @@ namespace Artemis.UI.PropertyInput IProfileEditorService profileEditorService, IModelValidator validator) : base(layerProperty, profileEditorService, validator) { - _startRegistration = layerProperty.GetDataBindingRegistration(range => range.Start); - _endRegistration = layerProperty.GetDataBindingRegistration(range => range.End); + _startRegistration = layerProperty.GetDataBindingRegistration("Start"); + _endRegistration = layerProperty.GetDataBindingRegistration("End"); } public float Start diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputView.xaml index 14fab93f1..4cae90771 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputView.xaml @@ -1,11 +1,10 @@ - { @@ -13,7 +13,7 @@ namespace Artemis.UI.PropertyInput public IntPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, IModelValidator validator) : base(layerProperty, profileEditorService, validator) { - _registration = layerProperty.GetDataBindingRegistration(value => value); + _registration = layerProperty.GetDataBindingRegistration("Value"); } public bool IsEnabled => _registration.DataBinding == null; diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputView.xaml index 62e4b8570..40a0e815a 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputView.xaml @@ -1,9 +1,8 @@ - { @@ -16,8 +16,8 @@ namespace Artemis.UI.PropertyInput IProfileEditorService profileEditorService, IModelValidator validator) : base(layerProperty, profileEditorService, validator) { - _startRegistration = layerProperty.GetDataBindingRegistration(range => range.Start); - _endRegistration = layerProperty.GetDataBindingRegistration(range => range.End); + _startRegistration = layerProperty.GetDataBindingRegistration("Start"); + _endRegistration = layerProperty.GetDataBindingRegistration("End"); } public int Start diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.xaml index fdc5a6c1b..6861259bb 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.xaml @@ -1,10 +1,9 @@ - { @@ -11,7 +11,7 @@ namespace Artemis.UI.PropertyInput public SKColorPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService) : base(layerProperty, profileEditorService) { - _registration = layerProperty.GetDataBindingRegistration(value => value); + _registration = layerProperty.GetDataBindingRegistration("Value"); } public bool IsEnabled => _registration.DataBinding == null; diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.xaml index 34e679a0c..1f15efc15 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.xaml @@ -1,11 +1,10 @@ - diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputViewModel.cs b/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputViewModel.cs index 8d7448f8b..ac32cbaf3 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputViewModel.cs +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputViewModel.cs @@ -6,7 +6,7 @@ using FluentValidation; using SkiaSharp; using Stylet; -namespace Artemis.UI.PropertyInput +namespace Artemis.UI.DefaultTypes.PropertyInput { public class SKPointPropertyInputViewModel : PropertyInputViewModel { @@ -16,8 +16,8 @@ namespace Artemis.UI.PropertyInput public SKPointPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, IModelValidator validator) : base(layerProperty, profileEditorService, validator) { - _xRegistration = layerProperty.GetDataBindingRegistration(point => point.X); - _yRegistration = layerProperty.GetDataBindingRegistration(point => point.Y); + _xRegistration = layerProperty.GetDataBindingRegistration("X"); + _yRegistration = layerProperty.GetDataBindingRegistration("Y"); } public float X diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputView.xaml index 9cca2e62b..0f5dfc93c 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputView.xaml @@ -1,9 +1,8 @@ - { @@ -18,8 +18,8 @@ namespace Artemis.UI.PropertyInput public SKSizePropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, IModelValidator validator) : base(layerProperty, profileEditorService, validator) { - _widthRegistration = layerProperty.GetDataBindingRegistration(size => size.Width); - _heightRegistration = layerProperty.GetDataBindingRegistration(size => size.Height); + _widthRegistration = layerProperty.GetDataBindingRegistration("Width"); + _heightRegistration = layerProperty.GetDataBindingRegistration("Height"); } // Since SKSize is immutable we need to create properties that replace the SKSize entirely diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs index 0ba67c4ab..b25710928 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(); @@ -106,8 +102,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings protected override void OnInitialActivate() { - base.OnInitialActivate(); Initialize(); + base.OnInitialActivate(); } private void Initialize() diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsView.xaml index 35e1c740c..164f28796 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsView.xaml @@ -6,11 +6,18 @@ xmlns:s="https://github.com/canton7/Stylet" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> - + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs index 23e73dcd1..00c72fc34 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs @@ -1,5 +1,8 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Artemis.Core; using Artemis.UI.Ninject.Factories; using Artemis.UI.Shared.Services; @@ -11,7 +14,9 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings { private readonly IDataBindingsVmFactory _dataBindingsVmFactory; private readonly IProfileEditorService _profileEditorService; + private ILayerProperty? _selectedDataBinding; private int _selectedItemIndex; + private bool _updating; public DataBindingsViewModel(IProfileEditorService profileEditorService, IDataBindingsVmFactory dataBindingsVmFactory) { @@ -24,9 +29,10 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings get => _selectedItemIndex; set => SetAndNotify(ref _selectedItemIndex, value); } - + private void CreateDataBindingViewModels() { + int oldIndex = SelectedItemIndex; Items.Clear(); ILayerProperty layerProperty = _profileEditorService.SelectedDataBinding; @@ -37,26 +43,64 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings // Create a data binding VM for each data bindable property. These VMs will be responsible for retrieving // and creating the actual data bindings - foreach (IDataBindingRegistration registration in registrations) - Items.Add(_dataBindingsVmFactory.DataBindingViewModel(registration)); + Items.AddRange(registrations.Select(registration => _dataBindingsVmFactory.DataBindingViewModel(registration))); - SelectedItemIndex = 0; + SelectedItemIndex = Items.Count < oldIndex ? 0 : oldIndex; } private void ProfileEditorServiceOnSelectedDataBindingChanged(object sender, EventArgs e) { CreateDataBindingViewModels(); + SubscribeToSelectedDataBinding(); + + SelectedItemIndex = 0; + } + + private void SubscribeToSelectedDataBinding() + { + if (_selectedDataBinding != null) + { + _selectedDataBinding.DataBindingPropertyRegistered -= DataBindingRegistrationsChanged; + _selectedDataBinding.DataBindingPropertiesCleared -= DataBindingRegistrationsChanged; + } + + _selectedDataBinding = _profileEditorService.SelectedDataBinding; + if (_selectedDataBinding != null) + { + _selectedDataBinding.DataBindingPropertyRegistered += DataBindingRegistrationsChanged; + _selectedDataBinding.DataBindingPropertiesCleared += DataBindingRegistrationsChanged; + } + } + + private void DataBindingRegistrationsChanged(object sender, LayerPropertyEventArgs e) + { + if (_updating) + return; + + _updating = true; + Execute.PostToUIThread(async () => + { + await Task.Delay(200); + CreateDataBindingViewModels(); + _updating = false; + }); } #region Overrides of Screen - /// protected override void OnInitialActivate() { _profileEditorService.SelectedDataBindingChanged += ProfileEditorServiceOnSelectedDataBindingChanged; CreateDataBindingViewModels(); + SubscribeToSelectedDataBinding(); base.OnInitialActivate(); } + + protected override void OnActivate() + { + SelectedItemIndex = 0; + base.OnActivate(); + } protected override void OnClose() { 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() diff --git a/src/Artemis.UI/Services/RegistrationService.cs b/src/Artemis.UI/Services/RegistrationService.cs index ffbdc2958..0d51ce376 100644 --- a/src/Artemis.UI/Services/RegistrationService.cs +++ b/src/Artemis.UI/Services/RegistrationService.cs @@ -4,9 +4,9 @@ using Artemis.Core.Services; using Artemis.UI.Controllers; using Artemis.UI.DefaultTypes.DataModel.Display; using Artemis.UI.DefaultTypes.DataModel.Input; +using Artemis.UI.DefaultTypes.PropertyInput; using Artemis.UI.InputProviders; using Artemis.UI.Ninject; -using Artemis.UI.PropertyInput; using Artemis.UI.Shared.Services; using Serilog;