From d37420e46275ec2957f9260a31a6c1358d5cdf58 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 8 Sep 2020 19:17:04 +0200 Subject: [PATCH 01/12] 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); } } From 4cfe71796c1e3122ac3738b6eb323dbdbb3f92c4 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Tue, 8 Sep 2020 23:54:19 +0200 Subject: [PATCH 02/12] Core wip things tired of writing descriptive messages ^^ --- src/Artemis.Core/Models/Profile/Layer.cs | 36 +------------------ .../Models/Profile/LayerPropertyGroup.cs | 9 +++++ .../Models/Profile/RenderProfileElement.cs | 26 -------------- .../LayerBrushes/Internal/BaseLayerBrush.cs | 2 +- .../Plugins/LayerBrushes/LayerBrush.cs | 4 +-- .../Plugins/LayerBrushes/PerLedLayerBrush.cs | 4 +-- .../Plugins/LayerBrushes/RgbNetLayerBrush.cs | 4 +-- .../LayerEffects/Internal/BaseLayerEffect.cs | 2 +- .../Plugins/LayerEffects/LayerEffect.cs | 4 +-- .../Services/RenderElementService.cs | 9 +++-- 10 files changed, 24 insertions(+), 76 deletions(-) diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index f58084cca..5470a4c46 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -118,27 +118,6 @@ namespace Artemis.Core return $"[Layer] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}"; } - /// - public override List GetAllKeyframes() - { - if (_disposed) - throw new ObjectDisposedException("Layer"); - - var keyframes = base.GetAllKeyframes(); - - foreach (var baseLayerProperty in General.GetAllLayerProperties()) - keyframes.AddRange(baseLayerProperty.BaseKeyframes); - foreach (var baseLayerProperty in Transform.GetAllLayerProperties()) - keyframes.AddRange(baseLayerProperty.BaseKeyframes); - if (LayerBrush?.BaseProperties != null) - { - foreach (var baseLayerProperty in LayerBrush.BaseProperties.GetAllLayerProperties()) - keyframes.AddRange(baseLayerProperty.BaseKeyframes); - } - - return keyframes; - } - /// protected override void Dispose(bool disposing) { @@ -311,19 +290,7 @@ namespace Artemis.Core baseLayerEffect.Update(delta); } } - - /// - public override List GetAllLayerProperties() - { - var result = base.GetAllLayerProperties(); - result.AddRange(General.GetAllLayerProperties()); - result.AddRange(Transform.GetAllLayerProperties()); - if (LayerBrush?.BaseProperties != null) - result.AddRange(LayerBrush.BaseProperties.GetAllLayerProperties()); - - return result; - } - + /// public override void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo) { @@ -383,7 +350,6 @@ namespace Artemis.Core if (Parent is Folder parentFolder) targetLocation = Path.Bounds.Location - parentFolder.Path.Bounds.Location; - canvas.DrawBitmap(_layerBitmap, targetLocation, layerPaint); } diff --git a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs index 8f0119c50..e753cd3d7 100644 --- a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs +++ b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs @@ -6,6 +6,7 @@ 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 @@ -170,6 +171,14 @@ namespace Artemis.Core OnPropertyGroupInitialized(); } + internal void InitializeDataBindings(IDataModelService dataModelService, IDataModelService dataModelService1) + { + foreach (var layerProperty in LayerProperties) + { + layerProperty.InitializeDataBindings(dataModelService); + } + } + internal void ApplyToEntity() { if (!PropertiesInitialized) diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs index 34aac49d9..a75ed2b56 100644 --- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs @@ -12,21 +12,6 @@ namespace Artemis.Core { public abstract class RenderProfileElement : ProfileElement { - /// - /// Returns a list of all keyframes on all properties and effects of this layer - /// - public virtual List GetAllKeyframes() - { - var keyframes = new List(); - foreach (var layerEffect in LayerEffects) - { - foreach (var baseLayerProperty in layerEffect.BaseProperties.GetAllLayerProperties()) - keyframes.AddRange(baseLayerProperty.BaseKeyframes); - } - - return keyframes; - } - protected void ApplyRenderElementDefaults() { MainSegmentLength = TimeSpan.FromSeconds(5); @@ -312,16 +297,5 @@ namespace Artemis.Core #endregion - /// - /// Returns all the layer properties of this profile element - /// - public virtual List GetAllLayerProperties() - { - var result = new List(); - foreach (var baseLayerEffect in LayerEffects) - result.AddRange(baseLayerEffect.BaseProperties.GetAllLayerProperties()); - - return result; - } } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs index 117a7656e..910432692 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs @@ -105,7 +105,7 @@ namespace Artemis.Core.LayerBrushes // Not only is this needed to initialize properties on the layer brushes, it also prevents implementing anything // but LayerBrush and RgbNetLayerBrush outside the core - internal abstract void Initialize(IRenderElementService renderElementService); + internal abstract void Initialize(); internal abstract void InternalRender(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint); diff --git a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/LayerBrush.cs index f1e75b77f..7ae5e268a 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrushes/LayerBrush.cs @@ -26,9 +26,9 @@ namespace Artemis.Core.LayerBrushes Render(canvas, canvasInfo, path, paint); } - internal override void Initialize(IRenderElementService renderElementService) + internal override void Initialize() { - InitializeProperties(renderElementService); + InitializeProperties(); } } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/LayerBrushes/PerLedLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/PerLedLayerBrush.cs index 13972ac87..8ad27213a 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/PerLedLayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrushes/PerLedLayerBrush.cs @@ -70,9 +70,9 @@ namespace Artemis.Core.LayerBrushes } } - internal override void Initialize(IRenderElementService renderElementService) + internal override void Initialize() { - InitializeProperties(renderElementService); + InitializeProperties(); } } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/LayerBrushes/RgbNetLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/RgbNetLayerBrush.cs index 1f270cf8e..395486165 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/RgbNetLayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrushes/RgbNetLayerBrush.cs @@ -46,12 +46,12 @@ namespace Artemis.Core.LayerBrushes LedGroup.Brush = GetBrush(); } - internal override void Initialize(IRenderElementService renderElementService) + internal override void Initialize() { LedGroup = new ListLedGroup(); Layer.RenderPropertiesUpdated += LayerOnRenderPropertiesUpdated; - InitializeProperties(renderElementService); + InitializeProperties(); UpdateLedGroup(); } diff --git a/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs b/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs index 06b63c649..f928cbab8 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs +++ b/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs @@ -147,6 +147,6 @@ namespace Artemis.Core.LayerEffects // Not only is this needed to initialize properties on the layer effects, it also prevents implementing anything // but LayerEffect outside the core - internal abstract void Initialize(IRenderElementService renderElementService); + internal abstract void Initialize(); } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/LayerEffects/LayerEffect.cs b/src/Artemis.Core/Plugins/LayerEffects/LayerEffect.cs index 03b43e8ee..ff7c6adec 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/LayerEffect.cs +++ b/src/Artemis.Core/Plugins/LayerEffects/LayerEffect.cs @@ -40,9 +40,9 @@ namespace Artemis.Core.LayerEffects EnableLayerEffect(); } - internal override void Initialize(IRenderElementService renderElementService) + internal override void Initialize() { - InitializeProperties(renderElementService); + InitializeProperties(); } } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/RenderElementService.cs b/src/Artemis.Core/Services/RenderElementService.cs index 9178e6c94..fed3caf7a 100644 --- a/src/Artemis.Core/Services/RenderElementService.cs +++ b/src/Artemis.Core/Services/RenderElementService.cs @@ -74,7 +74,7 @@ namespace Artemis.Core.Services var brush = (BaseLayerBrush) _kernel.Get(descriptor.LayerBrushType); brush.Layer = layer; brush.Descriptor = descriptor; - brush.Initialize(this); + brush.Initialize(); brush.Update(0); layer.LayerBrush = brush; @@ -94,7 +94,7 @@ namespace Artemis.Core.Services effect.Order = renderElement.LayerEffects.Count + 1; effect.Descriptor = layerEffectDescriptor; - effect.Initialize(this); + effect.Initialize(); effect.Update(0); renderElement.AddLayerEffect(effect); @@ -135,7 +135,7 @@ namespace Artemis.Core.Services effect.Enabled = layerEffectEntity.Enabled; effect.Descriptor = descriptor; - effect.Initialize(this); + effect.Initialize(); effect.Update(0); renderElement.AddLayerEffect(effect); @@ -162,8 +162,7 @@ namespace Artemis.Core.Services public void InstantiateDataBindings(RenderProfileElement renderElement) { - foreach (var baseLayerProperty in renderElement.GetAllLayerProperties()) - baseLayerProperty.InitializeDataBindings(_dataModelService, _dataBindingService); + renderElement.InitializeDataBindings(_dataBindingService, _dataModelService); } } } \ No newline at end of file From 11de30318e0eb91fc38ea3bd34ebfd39052567fc Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 9 Sep 2020 19:56:06 +0200 Subject: [PATCH 03/12] Core - Added stores for the different register-able types Profiles - Refactored large parts of the profile structure to use these stores --- .../Artemis.Core.csproj.DotSettings | 7 + .../Events/{ => Plugins}/PluginEventArgs.cs | 0 .../DataBindingPropertyUpdatedEvent.cs | 0 .../{ => Profiles}/LayerPropertyEventArgs.cs | 0 .../LayerPropertyGroupUpdatingEventArgs.cs | 0 .../Stores/ConditionOperatorStoreEvent.cs | 12 + .../DataBindingModifierTypeStoreEvent.cs | 12 + .../Events/Stores/DataModelStoreEvent.cs | 12 + .../Events/Stores/LayerBrushStoreEvent.cs | 12 + .../Events/Stores/LayerEffectStoreEvent.cs | 12 + .../Abstract/DisplayConditionPart.cs | 1 - ...ditionOperator.cs => ConditionOperator.cs} | 38 +-- .../Conditions/DisplayConditionGroup.cs | 7 - .../Conditions/DisplayConditionList.cs | 11 +- .../DisplayConditionListPredicate.cs | 23 +- .../Conditions/DisplayConditionPredicate.cs | 31 ++- .../Operators/EqualsConditionOperator.cs | 2 +- .../Operators/GreaterThanConditionOperator.cs | 2 +- .../GreaterThanOrEqualConditionOperator.cs | 2 +- .../Operators/LessThanConditionOperator.cs | 2 +- .../LessThanOrEqualConditionOperator.cs | 2 +- .../Operators/NotEqualConditionOperator.cs | 2 +- .../StringContainsConditionOperator.cs | 2 +- .../StringEndsWithConditionOperator.cs | 2 +- .../StringEqualsConditionOperator.cs | 2 +- .../StringNotContainsConditionOperator.cs | 2 +- .../StringNotEqualConditionOperator.cs | 2 +- .../Operators/StringNullConditionOperator.cs | 2 +- .../StringStartsWithConditionOperator.cs | 2 +- src/Artemis.Core/Models/Profile/Folder.cs | 49 ++-- src/Artemis.Core/Models/Profile/Layer.cs | 113 +++++--- .../Models/Profile/LayerPropertyGroup.cs | 8 - src/Artemis.Core/Models/Profile/Profile.cs | 6 +- .../Models/Profile/ProfileElement.cs | 113 +++++--- .../Models/Profile/RenderProfileElement.cs | 68 +++-- .../Plugins/DataModelExpansions/DataModel.cs | 6 + .../DataModelExpansions/DataModelExpansion.cs | 3 +- .../LayerBrushes/LayerBrushDescriptor.cs | 24 ++ .../LayerEffects/LayerEffectDescriptor.cs | 32 +++ src/Artemis.Core/Plugins/Modules/Module.cs | 1 + .../Plugins/Modules/ProfileModule.cs | 3 +- src/Artemis.Core/Services/CoreService.cs | 9 +- .../Services/DataBindingService.cs | 106 -------- src/Artemis.Core/Services/DataModelService.cs | 252 ------------------ .../Services/Interfaces/ICoreService.cs | 3 + .../Services/Interfaces/IDataModelService.cs | 91 ------- .../Services/Interfaces/IDeviceService.cs | 3 + .../Interfaces/IRenderElementService.cs | 59 ---- .../Services/Interfaces/IRgbService.cs | 17 +- .../Registration/ConditionOperatorService.cs | 64 +++++ .../Registration/DataBindingService.cs | 49 ++++ .../Services/Registration/DataModelService.cs | 86 ++++++ .../Interfaces/IConditionOperatorService.cs | 37 +++ .../Interfaces/IDataBindingService.cs | 22 +- .../Interfaces/IDataModelService.cs | 46 ++++ .../Interfaces/ILayerBrushService.cs | 26 ++ .../Interfaces/ILayerEffectService.cs | 27 ++ .../Registration/LayerBrushService.cs | 38 +++ .../Registration/LayerEffectService.cs | 39 +++ .../Services/RenderElementService.cs | 168 ------------ src/Artemis.Core/Services/RgbService.cs | 2 +- .../Services/Storage/ProfileService.cs | 6 +- .../Stores/ConditionOperatorStore.cs | 90 +++++++ .../Stores/DataBindingModifierTypeStore.cs | 90 +++++++ src/Artemis.Core/Stores/DataModelStore.cs | 75 ++++++ src/Artemis.Core/Stores/LayerBrushStore.cs | 75 ++++++ src/Artemis.Core/Stores/LayerEffectStore.cs | 75 ++++++ .../ConditionOperatorRegistration.cs | 40 +++ .../DataBindingModifierTypeRegistration.cs | 40 +++ .../Registrations/DataModelRegistration.cs | 41 +++ .../Registrations/LayerBrushRegistration.cs | 41 +++ .../Registrations/LayerEffectRegistration.cs | 41 +++ .../Utilities/DeserializationLogger.cs | 44 +++ .../DisplayConditionListPredicateViewModel.cs | 14 +- .../DisplayConditionPredicateViewModel.cs | 14 +- 75 files changed, 1526 insertions(+), 934 deletions(-) rename src/Artemis.Core/Events/{ => Plugins}/PluginEventArgs.cs (100%) rename src/Artemis.Core/Events/{ => Profiles}/DataBindingPropertyUpdatedEvent.cs (100%) rename src/Artemis.Core/Events/{ => Profiles}/LayerPropertyEventArgs.cs (100%) rename src/Artemis.Core/Events/{ => Profiles}/LayerPropertyGroupUpdatingEventArgs.cs (100%) create mode 100644 src/Artemis.Core/Events/Stores/ConditionOperatorStoreEvent.cs create mode 100644 src/Artemis.Core/Events/Stores/DataBindingModifierTypeStoreEvent.cs create mode 100644 src/Artemis.Core/Events/Stores/DataModelStoreEvent.cs create mode 100644 src/Artemis.Core/Events/Stores/LayerBrushStoreEvent.cs create mode 100644 src/Artemis.Core/Events/Stores/LayerEffectStoreEvent.cs rename src/Artemis.Core/Models/Profile/Conditions/{DisplayConditionOperator.cs => ConditionOperator.cs} (61%) delete mode 100644 src/Artemis.Core/Services/DataBindingService.cs delete mode 100644 src/Artemis.Core/Services/DataModelService.cs delete mode 100644 src/Artemis.Core/Services/Interfaces/IDataModelService.cs delete mode 100644 src/Artemis.Core/Services/Interfaces/IRenderElementService.cs create mode 100644 src/Artemis.Core/Services/Registration/ConditionOperatorService.cs create mode 100644 src/Artemis.Core/Services/Registration/DataBindingService.cs create mode 100644 src/Artemis.Core/Services/Registration/DataModelService.cs create mode 100644 src/Artemis.Core/Services/Registration/Interfaces/IConditionOperatorService.cs rename src/Artemis.Core/Services/{ => Registration}/Interfaces/IDataBindingService.cs (60%) create mode 100644 src/Artemis.Core/Services/Registration/Interfaces/IDataModelService.cs create mode 100644 src/Artemis.Core/Services/Registration/Interfaces/ILayerBrushService.cs create mode 100644 src/Artemis.Core/Services/Registration/Interfaces/ILayerEffectService.cs create mode 100644 src/Artemis.Core/Services/Registration/LayerBrushService.cs create mode 100644 src/Artemis.Core/Services/Registration/LayerEffectService.cs delete mode 100644 src/Artemis.Core/Services/RenderElementService.cs create mode 100644 src/Artemis.Core/Stores/ConditionOperatorStore.cs create mode 100644 src/Artemis.Core/Stores/DataBindingModifierTypeStore.cs create mode 100644 src/Artemis.Core/Stores/DataModelStore.cs create mode 100644 src/Artemis.Core/Stores/LayerBrushStore.cs create mode 100644 src/Artemis.Core/Stores/LayerEffectStore.cs create mode 100644 src/Artemis.Core/Stores/Registrations/ConditionOperatorRegistration.cs create mode 100644 src/Artemis.Core/Stores/Registrations/DataBindingModifierTypeRegistration.cs create mode 100644 src/Artemis.Core/Stores/Registrations/DataModelRegistration.cs create mode 100644 src/Artemis.Core/Stores/Registrations/LayerBrushRegistration.cs create mode 100644 src/Artemis.Core/Stores/Registrations/LayerEffectRegistration.cs create mode 100644 src/Artemis.Core/Utilities/DeserializationLogger.cs diff --git a/src/Artemis.Core/Artemis.Core.csproj.DotSettings b/src/Artemis.Core/Artemis.Core.csproj.DotSettings index 77021d436..7f651acb6 100644 --- a/src/Artemis.Core/Artemis.Core.csproj.DotSettings +++ b/src/Artemis.Core/Artemis.Core.csproj.DotSettings @@ -1,5 +1,8 @@  True + True + True + True True True True @@ -29,6 +32,10 @@ True True True + True + True True True + True + True True \ No newline at end of file diff --git a/src/Artemis.Core/Events/PluginEventArgs.cs b/src/Artemis.Core/Events/Plugins/PluginEventArgs.cs similarity index 100% rename from src/Artemis.Core/Events/PluginEventArgs.cs rename to src/Artemis.Core/Events/Plugins/PluginEventArgs.cs diff --git a/src/Artemis.Core/Events/DataBindingPropertyUpdatedEvent.cs b/src/Artemis.Core/Events/Profiles/DataBindingPropertyUpdatedEvent.cs similarity index 100% rename from src/Artemis.Core/Events/DataBindingPropertyUpdatedEvent.cs rename to src/Artemis.Core/Events/Profiles/DataBindingPropertyUpdatedEvent.cs diff --git a/src/Artemis.Core/Events/LayerPropertyEventArgs.cs b/src/Artemis.Core/Events/Profiles/LayerPropertyEventArgs.cs similarity index 100% rename from src/Artemis.Core/Events/LayerPropertyEventArgs.cs rename to src/Artemis.Core/Events/Profiles/LayerPropertyEventArgs.cs diff --git a/src/Artemis.Core/Events/LayerPropertyGroupUpdatingEventArgs.cs b/src/Artemis.Core/Events/Profiles/LayerPropertyGroupUpdatingEventArgs.cs similarity index 100% rename from src/Artemis.Core/Events/LayerPropertyGroupUpdatingEventArgs.cs rename to src/Artemis.Core/Events/Profiles/LayerPropertyGroupUpdatingEventArgs.cs diff --git a/src/Artemis.Core/Events/Stores/ConditionOperatorStoreEvent.cs b/src/Artemis.Core/Events/Stores/ConditionOperatorStoreEvent.cs new file mode 100644 index 000000000..fb81fa23f --- /dev/null +++ b/src/Artemis.Core/Events/Stores/ConditionOperatorStoreEvent.cs @@ -0,0 +1,12 @@ +namespace Artemis.Core +{ + internal class ConditionOperatorStoreEvent + { + public ConditionOperatorStoreEvent(ConditionOperatorRegistration registration) + { + Registration = registration; + } + + public ConditionOperatorRegistration Registration { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Events/Stores/DataBindingModifierTypeStoreEvent.cs b/src/Artemis.Core/Events/Stores/DataBindingModifierTypeStoreEvent.cs new file mode 100644 index 000000000..c954a7f60 --- /dev/null +++ b/src/Artemis.Core/Events/Stores/DataBindingModifierTypeStoreEvent.cs @@ -0,0 +1,12 @@ +namespace Artemis.Core +{ + internal class DataBindingModifierTypeStoreEvent + { + public DataBindingModifierTypeStoreEvent(DataBindingModifierTypeRegistration typeRegistration) + { + TypeRegistration = typeRegistration; + } + + public DataBindingModifierTypeRegistration TypeRegistration { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Events/Stores/DataModelStoreEvent.cs b/src/Artemis.Core/Events/Stores/DataModelStoreEvent.cs new file mode 100644 index 000000000..eb1132422 --- /dev/null +++ b/src/Artemis.Core/Events/Stores/DataModelStoreEvent.cs @@ -0,0 +1,12 @@ +namespace Artemis.Core +{ + internal class DataModelStoreEvent + { + public DataModelStoreEvent(DataModelRegistration registration) + { + Registration = registration; + } + + public DataModelRegistration Registration { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Events/Stores/LayerBrushStoreEvent.cs b/src/Artemis.Core/Events/Stores/LayerBrushStoreEvent.cs new file mode 100644 index 000000000..6d80a46fb --- /dev/null +++ b/src/Artemis.Core/Events/Stores/LayerBrushStoreEvent.cs @@ -0,0 +1,12 @@ +namespace Artemis.Core +{ + internal class LayerBrushStoreEvent + { + public LayerBrushStoreEvent(LayerBrushRegistration registration) + { + Registration = registration; + } + + public LayerBrushRegistration Registration { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Events/Stores/LayerEffectStoreEvent.cs b/src/Artemis.Core/Events/Stores/LayerEffectStoreEvent.cs new file mode 100644 index 000000000..8abcecb7f --- /dev/null +++ b/src/Artemis.Core/Events/Stores/LayerEffectStoreEvent.cs @@ -0,0 +1,12 @@ +namespace Artemis.Core +{ + internal class LayerEffectStoreEvent + { + public LayerEffectStoreEvent(LayerEffectRegistration registration) + { + Registration = registration; + } + + public LayerEffectRegistration Registration { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/Abstract/DisplayConditionPart.cs b/src/Artemis.Core/Models/Profile/Conditions/Abstract/DisplayConditionPart.cs index 2ae490f56..2669afc17 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/Abstract/DisplayConditionPart.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Abstract/DisplayConditionPart.cs @@ -60,7 +60,6 @@ namespace Artemis.Core /// public abstract bool EvaluateObject(object target); - internal abstract void Initialize(IDataModelService dataModelService); internal abstract void ApplyToEntity(); internal abstract DisplayConditionPartEntity GetEntity(); } diff --git a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionOperator.cs b/src/Artemis.Core/Models/Profile/Conditions/ConditionOperator.cs similarity index 61% rename from src/Artemis.Core/Models/Profile/Conditions/DisplayConditionOperator.cs rename to src/Artemis.Core/Models/Profile/Conditions/ConditionOperator.cs index cb592b5e5..53ca9bd79 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionOperator.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/ConditionOperator.cs @@ -2,18 +2,14 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using Artemis.Core.Services; namespace Artemis.Core { /// - /// A display condition operator is used by the conditions system to perform a specific boolean check + /// A condition operator is used by the conditions system to perform a specific boolean check /// - public abstract class DisplayConditionOperator + public abstract class ConditionOperator { - private IDataModelService _dataModelService; - private bool _registered; - /// /// Gets the plugin info this condition operator belongs to /// Note: Not set until after registering @@ -57,35 +53,5 @@ namespace Artemis.Core /// The parameter on the right side of the expression /// public abstract BinaryExpression CreateExpression(Expression leftSide, Expression rightSide); - - internal void Register(PluginInfo pluginInfo, IDataModelService dataModelService) - { - if (_registered) - return; - - PluginInfo = pluginInfo; - _dataModelService = dataModelService; - - if (PluginInfo != Constants.CorePluginInfo) - PluginInfo.Instance.PluginDisabled += InstanceOnPluginDisabled; - - _registered = true; - } - - internal void Unsubscribe() - { - if (!_registered) - return; - - if (PluginInfo != Constants.CorePluginInfo) - PluginInfo.Instance.PluginDisabled -= InstanceOnPluginDisabled; - _registered = false; - } - - private void InstanceOnPluginDisabled(object sender, EventArgs e) - { - // The service will call Unsubscribe - _dataModelService.RemoveConditionOperator(this); - } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionGroup.cs b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionGroup.cs index c93bb0612..e68dd9e57 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionGroup.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionGroup.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using Artemis.Core.Services; using Artemis.Storage.Entities.Profile.Abstract; using Artemis.Storage.Entities.Profile.Conditions; @@ -108,12 +107,6 @@ namespace Artemis.Core child.ApplyToEntity(); } - internal override void Initialize(IDataModelService dataModelService) - { - foreach (var child in Children) - child.Initialize(dataModelService); - } - internal override DisplayConditionPartEntity GetEntity() { return Entity; diff --git a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionList.cs b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionList.cs index 4bb1a587d..bd9c54b76 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionList.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionList.cs @@ -3,7 +3,6 @@ using System.Collections; using System.Linq; using System.Linq.Expressions; using Artemis.Core.DataModelExpansions; -using Artemis.Core.Services; using Artemis.Storage.Entities.Profile.Abstract; using Artemis.Storage.Entities.Profile.Conditions; @@ -15,6 +14,8 @@ namespace Artemis.Core { Parent = parent; Entity = new DisplayConditionListEntity(); + + Initialize(); } public DisplayConditionList(DisplayConditionPart parent, DisplayConditionListEntity entity) @@ -22,6 +23,8 @@ namespace Artemis.Core Parent = parent; Entity = entity; ListOperator = (ListOperator) entity.ListOperator; + + Initialize(); } public DisplayConditionListEntity Entity { get; set; } @@ -120,13 +123,13 @@ namespace Artemis.Core return Entity; } - internal override void Initialize(IDataModelService dataModelService) + internal void Initialize() { if (Entity.ListDataModelGuid == null) return; // Get the data model and ensure the path is valid - var dataModel = dataModelService.GetPluginDataModelByGuid(Entity.ListDataModelGuid.Value); + var dataModel = DataModelStore.Get(Entity.ListDataModelGuid.Value)?.DataModel; if (dataModel == null || !dataModel.ContainsPath(Entity.ListPropertyPath)) return; @@ -143,8 +146,6 @@ namespace Artemis.Core Entity.Children.Clear(); AddChild(new DisplayConditionGroup(this)); } - - Children[0].Initialize(dataModelService); } } diff --git a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionListPredicate.cs b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionListPredicate.cs index 6afee4a2f..41c5890df 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionListPredicate.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionListPredicate.cs @@ -2,7 +2,6 @@ using System.Linq; using System.Linq.Expressions; using Artemis.Core.DataModelExpansions; -using Artemis.Core.Services; using Artemis.Storage.Entities.Profile.Abstract; using Artemis.Storage.Entities.Profile.Conditions; using Newtonsoft.Json; @@ -18,6 +17,7 @@ namespace Artemis.Core Entity = new DisplayConditionListPredicateEntity(); ApplyParentList(); + Initialize(); } public DisplayConditionListPredicate(DisplayConditionPart parent, DisplayConditionListPredicateEntity entity) @@ -27,12 +27,13 @@ namespace Artemis.Core PredicateType = (ProfileRightSideType) entity.PredicateType; ApplyParentList(); + Initialize(); } public DisplayConditionListPredicateEntity Entity { get; set; } public ProfileRightSideType PredicateType { get; set; } - public DisplayConditionOperator Operator { get; private set; } + public ConditionOperator Operator { get; private set; } public Type ListType { get; private set; } public DataModel ListDataModel { get; private set; } @@ -120,9 +121,9 @@ namespace Artemis.Core CreateExpression(); } - public void UpdateOperator(DisplayConditionOperator displayConditionOperator) + public void UpdateOperator(ConditionOperator conditionOperator) { - if (displayConditionOperator == null) + if (conditionOperator == null) { Operator = null; return; @@ -130,13 +131,13 @@ namespace Artemis.Core if (LeftPropertyPath == null) { - Operator = displayConditionOperator; + Operator = conditionOperator; return; } var leftType = GetTypeAtInnerPath(LeftPropertyPath); - if (displayConditionOperator.SupportsType(leftType)) - Operator = displayConditionOperator; + if (conditionOperator.SupportsType(leftType)) + Operator = conditionOperator; CreateExpression(); } @@ -203,7 +204,7 @@ namespace Artemis.Core Entity.OperatorType = Operator?.GetType().Name; } - internal override void Initialize(IDataModelService dataModelService) + internal void Initialize() { // Left side if (Entity.LeftPropertyPath != null && ListContainsInnerPath(Entity.LeftPropertyPath)) @@ -212,7 +213,7 @@ namespace Artemis.Core // Operator if (Entity.OperatorPluginGuid != null) { - var conditionOperator = dataModelService.GetConditionOperator(Entity.OperatorPluginGuid.Value, Entity.OperatorType); + var conditionOperator = ConditionOperatorStore.Get(Entity.OperatorPluginGuid.Value, Entity.OperatorType)?.ConditionOperator; if (conditionOperator != null) UpdateOperator(conditionOperator); } @@ -241,7 +242,7 @@ namespace Artemis.Core // If deserialization fails, use the type's default catch (JsonSerializationException e) { - dataModelService.LogListPredicateDeserializationFailure(this, e); + DeserializationLogger.LogListPredicateDeserializationFailure(this, e); rightSideValue = Activator.CreateInstance(leftSideType); } @@ -255,7 +256,7 @@ namespace Artemis.Core } catch (JsonException e) { - dataModelService.LogListPredicateDeserializationFailure(this, e); + DeserializationLogger.LogListPredicateDeserializationFailure(this, e); } } } diff --git a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs index 617de25f1..70a7a890a 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs @@ -2,7 +2,6 @@ using System.Linq; using System.Linq.Expressions; using Artemis.Core.DataModelExpansions; -using Artemis.Core.Services; using Artemis.Storage.Entities.Profile.Abstract; using Artemis.Storage.Entities.Profile.Conditions; using Newtonsoft.Json; @@ -25,6 +24,8 @@ namespace Artemis.Core Parent = parent; PredicateType = predicateType; Entity = new DisplayConditionPredicateEntity(); + + Initialize(); } /// @@ -37,6 +38,8 @@ namespace Artemis.Core Parent = parent; Entity = entity; PredicateType = (ProfileRightSideType) entity.PredicateType; + + Initialize(); } /// @@ -47,7 +50,7 @@ namespace Artemis.Core /// /// Gets the operator /// - public DisplayConditionOperator Operator { get; private set; } + public ConditionOperator Operator { get; private set; } /// /// Gets the currently used instance of the left data model @@ -175,11 +178,11 @@ namespace Artemis.Core /// /// Updates the operator of the predicate and re-compiles the expression /// - /// - public void UpdateOperator(DisplayConditionOperator displayConditionOperator) + /// + public void UpdateOperator(ConditionOperator conditionOperator) { // Calling CreateExpression will clear compiled expressions - if (displayConditionOperator == null) + if (conditionOperator == null) { Operator = null; CreateExpression(); @@ -189,18 +192,18 @@ namespace Artemis.Core // No need to clear compiled expressions, without a left data model they are already null if (LeftDataModel == null) { - Operator = displayConditionOperator; + Operator = conditionOperator; return; } var leftType = LeftDataModel.GetTypeAtPath(LeftPropertyPath); - if (!displayConditionOperator.SupportsType(leftType)) + if (!conditionOperator.SupportsType(leftType)) { - throw new ArtemisCoreException($"Cannot apply operator {displayConditionOperator.GetType().Name} to this predicate because " + + throw new ArtemisCoreException($"Cannot apply operator {conditionOperator.GetType().Name} to this predicate because " + $"it does not support left side type {leftType.Name}"); } - Operator = displayConditionOperator; + Operator = conditionOperator; CreateExpression(); } @@ -243,12 +246,12 @@ namespace Artemis.Core Entity.OperatorType = Operator?.GetType().Name; } - internal override void Initialize(IDataModelService dataModelService) + internal void Initialize() { // Left side if (Entity.LeftDataModelGuid != null) { - var dataModel = dataModelService.GetPluginDataModelByGuid(Entity.LeftDataModelGuid.Value); + var dataModel = DataModelStore.Get(Entity.LeftDataModelGuid.Value)?.DataModel; if (dataModel != null && dataModel.ContainsPath(Entity.LeftPropertyPath)) UpdateLeftSide(dataModel, Entity.LeftPropertyPath); } @@ -256,7 +259,7 @@ namespace Artemis.Core // Operator if (Entity.OperatorPluginGuid != null) { - var conditionOperator = dataModelService.GetConditionOperator(Entity.OperatorPluginGuid.Value, Entity.OperatorType); + var conditionOperator = ConditionOperatorStore.Get(Entity.OperatorPluginGuid.Value, Entity.OperatorType)?.ConditionOperator; if (conditionOperator != null) UpdateOperator(conditionOperator); } @@ -264,7 +267,7 @@ namespace Artemis.Core // Right side dynamic if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightDataModelGuid != null) { - var dataModel = dataModelService.GetPluginDataModelByGuid(Entity.RightDataModelGuid.Value); + var dataModel = DataModelStore.Get(Entity.RightDataModelGuid.Value)?.DataModel; if (dataModel != null && dataModel.ContainsPath(Entity.RightPropertyPath)) UpdateRightSide(dataModel, Entity.RightPropertyPath); } @@ -286,7 +289,7 @@ namespace Artemis.Core // If deserialization fails, use the type's default catch (JsonSerializationException e) { - dataModelService.LogPredicateDeserializationFailure(this, e); + DeserializationLogger.LogPredicateDeserializationFailure(this, e); rightSideValue = Activator.CreateInstance(leftSideType); } diff --git a/src/Artemis.Core/Models/Profile/Conditions/Operators/EqualsConditionOperator.cs b/src/Artemis.Core/Models/Profile/Conditions/Operators/EqualsConditionOperator.cs index 44388b7c1..87b60d484 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/Operators/EqualsConditionOperator.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Operators/EqualsConditionOperator.cs @@ -4,7 +4,7 @@ using System.Linq.Expressions; namespace Artemis.Core { - internal class EqualsConditionOperator : DisplayConditionOperator + internal class EqualsConditionOperator : ConditionOperator { public override IReadOnlyCollection CompatibleTypes => new List {typeof(object)}; diff --git a/src/Artemis.Core/Models/Profile/Conditions/Operators/GreaterThanConditionOperator.cs b/src/Artemis.Core/Models/Profile/Conditions/Operators/GreaterThanConditionOperator.cs index 37dbccadb..6fa3287a5 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/Operators/GreaterThanConditionOperator.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Operators/GreaterThanConditionOperator.cs @@ -4,7 +4,7 @@ using System.Linq.Expressions; namespace Artemis.Core { - internal class GreaterThanConditionOperator : DisplayConditionOperator + internal class GreaterThanConditionOperator : ConditionOperator { public override IReadOnlyCollection CompatibleTypes => Constants.NumberTypes; diff --git a/src/Artemis.Core/Models/Profile/Conditions/Operators/GreaterThanOrEqualConditionOperator.cs b/src/Artemis.Core/Models/Profile/Conditions/Operators/GreaterThanOrEqualConditionOperator.cs index 578c20aab..5bd914abb 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/Operators/GreaterThanOrEqualConditionOperator.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Operators/GreaterThanOrEqualConditionOperator.cs @@ -4,7 +4,7 @@ using System.Linq.Expressions; namespace Artemis.Core { - internal class GreaterThanOrEqualConditionOperator : DisplayConditionOperator + internal class GreaterThanOrEqualConditionOperator : ConditionOperator { public override IReadOnlyCollection CompatibleTypes => Constants.NumberTypes; diff --git a/src/Artemis.Core/Models/Profile/Conditions/Operators/LessThanConditionOperator.cs b/src/Artemis.Core/Models/Profile/Conditions/Operators/LessThanConditionOperator.cs index 244b15c08..72567b9a9 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/Operators/LessThanConditionOperator.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Operators/LessThanConditionOperator.cs @@ -4,7 +4,7 @@ using System.Linq.Expressions; namespace Artemis.Core { - internal class LessThanConditionOperator : DisplayConditionOperator + internal class LessThanConditionOperator : ConditionOperator { public override IReadOnlyCollection CompatibleTypes => Constants.NumberTypes; diff --git a/src/Artemis.Core/Models/Profile/Conditions/Operators/LessThanOrEqualConditionOperator.cs b/src/Artemis.Core/Models/Profile/Conditions/Operators/LessThanOrEqualConditionOperator.cs index e47b3f018..fd344dbf3 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/Operators/LessThanOrEqualConditionOperator.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Operators/LessThanOrEqualConditionOperator.cs @@ -4,7 +4,7 @@ using System.Linq.Expressions; namespace Artemis.Core { - internal class LessThanOrEqualConditionOperator : DisplayConditionOperator + internal class LessThanOrEqualConditionOperator : ConditionOperator { public override IReadOnlyCollection CompatibleTypes => Constants.NumberTypes; diff --git a/src/Artemis.Core/Models/Profile/Conditions/Operators/NotEqualConditionOperator.cs b/src/Artemis.Core/Models/Profile/Conditions/Operators/NotEqualConditionOperator.cs index a1a9934be..4af94147c 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/Operators/NotEqualConditionOperator.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Operators/NotEqualConditionOperator.cs @@ -4,7 +4,7 @@ using System.Linq.Expressions; namespace Artemis.Core { - internal class NotEqualConditionOperator : DisplayConditionOperator + internal class NotEqualConditionOperator : ConditionOperator { public override IReadOnlyCollection CompatibleTypes => new List {typeof(object)}; diff --git a/src/Artemis.Core/Models/Profile/Conditions/Operators/StringContainsConditionOperator.cs b/src/Artemis.Core/Models/Profile/Conditions/Operators/StringContainsConditionOperator.cs index 758e0400e..3ef231cfd 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/Operators/StringContainsConditionOperator.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Operators/StringContainsConditionOperator.cs @@ -5,7 +5,7 @@ using System.Reflection; namespace Artemis.Core { - internal class StringContainsConditionOperator : DisplayConditionOperator + internal class StringContainsConditionOperator : ConditionOperator { private readonly MethodInfo _contains; private readonly MethodInfo _toLower; diff --git a/src/Artemis.Core/Models/Profile/Conditions/Operators/StringEndsWithConditionOperator.cs b/src/Artemis.Core/Models/Profile/Conditions/Operators/StringEndsWithConditionOperator.cs index 38ed72633..1903984e9 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/Operators/StringEndsWithConditionOperator.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Operators/StringEndsWithConditionOperator.cs @@ -5,7 +5,7 @@ using System.Reflection; namespace Artemis.Core { - internal class StringEndsWithConditionOperator : DisplayConditionOperator + internal class StringEndsWithConditionOperator : ConditionOperator { private readonly MethodInfo _endsWith; private readonly MethodInfo _toLower; diff --git a/src/Artemis.Core/Models/Profile/Conditions/Operators/StringEqualsConditionOperator.cs b/src/Artemis.Core/Models/Profile/Conditions/Operators/StringEqualsConditionOperator.cs index 91a58ec91..cc0cf432c 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/Operators/StringEqualsConditionOperator.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Operators/StringEqualsConditionOperator.cs @@ -5,7 +5,7 @@ using System.Reflection; namespace Artemis.Core { - internal class StringEqualsConditionOperator : DisplayConditionOperator + internal class StringEqualsConditionOperator : ConditionOperator { private readonly MethodInfo _toLower; diff --git a/src/Artemis.Core/Models/Profile/Conditions/Operators/StringNotContainsConditionOperator.cs b/src/Artemis.Core/Models/Profile/Conditions/Operators/StringNotContainsConditionOperator.cs index 671555eb5..97fbac2eb 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/Operators/StringNotContainsConditionOperator.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Operators/StringNotContainsConditionOperator.cs @@ -5,7 +5,7 @@ using System.Reflection; namespace Artemis.Core { - internal class StringNotContainsConditionOperator : DisplayConditionOperator + internal class StringNotContainsConditionOperator : ConditionOperator { private readonly MethodInfo _contains; private readonly MethodInfo _toLower; diff --git a/src/Artemis.Core/Models/Profile/Conditions/Operators/StringNotEqualConditionOperator.cs b/src/Artemis.Core/Models/Profile/Conditions/Operators/StringNotEqualConditionOperator.cs index c98d4bef3..33e14b4e1 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/Operators/StringNotEqualConditionOperator.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Operators/StringNotEqualConditionOperator.cs @@ -5,7 +5,7 @@ using System.Reflection; namespace Artemis.Core { - internal class StringNotEqualConditionOperator : DisplayConditionOperator + internal class StringNotEqualConditionOperator : ConditionOperator { private readonly MethodInfo _toLower; diff --git a/src/Artemis.Core/Models/Profile/Conditions/Operators/StringNullConditionOperator.cs b/src/Artemis.Core/Models/Profile/Conditions/Operators/StringNullConditionOperator.cs index bb0b99d42..6c51024ed 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/Operators/StringNullConditionOperator.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Operators/StringNullConditionOperator.cs @@ -4,7 +4,7 @@ using System.Linq.Expressions; namespace Artemis.Core { - internal class StringNullConditionOperator : DisplayConditionOperator + internal class StringNullConditionOperator : ConditionOperator { public StringNullConditionOperator() { diff --git a/src/Artemis.Core/Models/Profile/Conditions/Operators/StringStartsWithConditionOperator.cs b/src/Artemis.Core/Models/Profile/Conditions/Operators/StringStartsWithConditionOperator.cs index f9f9fbf8e..4b3d9f39b 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/Operators/StringStartsWithConditionOperator.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Operators/StringStartsWithConditionOperator.cs @@ -5,7 +5,7 @@ using System.Reflection; namespace Artemis.Core { - internal class StringStartsWithConditionOperator : DisplayConditionOperator + internal class StringStartsWithConditionOperator : ConditionOperator { private readonly MethodInfo _startsWith; private readonly MethodInfo _toLower; diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs index 5671181ca..1436f7bcf 100644 --- a/src/Artemis.Core/Models/Profile/Folder.cs +++ b/src/Artemis.Core/Models/Profile/Folder.cs @@ -42,21 +42,7 @@ namespace Artemis.Core _layerEffects = new List(); _expandedPropertyGroups = new List(); - _expandedPropertyGroups.AddRange(folderEntity.ExpandedPropertyGroups); - - // Load child folders - foreach (var childFolder in Profile.ProfileEntity.Folders.Where(f => f.ParentId == EntityId)) - ChildrenList.Add(new Folder(profile, this, childFolder)); - // Load child layers - foreach (var childLayer in Profile.ProfileEntity.Layers.Where(f => f.ParentId == EntityId)) - ChildrenList.Add(new Layer(profile, this, childLayer)); - - // Ensure order integrity, should be unnecessary but no one is perfect specially me - ChildrenList = ChildrenList.OrderBy(c => c.Order).ToList(); - for (var index = 0; index < ChildrenList.Count; index++) - ChildrenList[index].Order = index + 1; - - ApplyRenderElementEntity(); + Load(); } internal FolderEntity FolderEntity { get; set; } @@ -263,23 +249,36 @@ namespace Artemis.Core if (!disposing) return; + _disposed = true; + foreach (var baseLayerEffect in LayerEffects) baseLayerEffect.Dispose(); - _layerEffects.Clear(); - foreach (var profileElement in Children) profileElement.Dispose(); - ChildrenList.Clear(); _folderBitmap?.Dispose(); - _folderBitmap = null; - - Profile = null; - _disposed = true; } + internal override void Load() + { + _expandedPropertyGroups.AddRange(FolderEntity.ExpandedPropertyGroups); - internal override void ApplyToEntity() + // Load child folders + foreach (var childFolder in Profile.ProfileEntity.Folders.Where(f => f.ParentId == EntityId)) + ChildrenList.Add(new Folder(Profile, this, childFolder)); + // Load child layers + foreach (var childLayer in Profile.ProfileEntity.Layers.Where(f => f.ParentId == EntityId)) + ChildrenList.Add(new Layer(Profile, this, childLayer)); + + // Ensure order integrity, should be unnecessary but no one is perfect specially me + ChildrenList = ChildrenList.OrderBy(c => c.Order).ToList(); + for (var index = 0; index < ChildrenList.Count; index++) + ChildrenList[index].Order = index + 1; + + LoadRenderElement(); + } + + internal override void Save() { if (_disposed) throw new ObjectDisposedException("Folder"); @@ -295,11 +294,11 @@ namespace Artemis.Core FolderEntity.ExpandedPropertyGroups.Clear(); FolderEntity.ExpandedPropertyGroups.AddRange(_expandedPropertyGroups); - ApplyRenderElementToEntity(); - // Conditions RenderElementEntity.RootDisplayCondition = DisplayConditionGroup?.Entity; DisplayConditionGroup?.ApplyToEntity(); + + SaveRenderElement(); } #region Events diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index 5470a4c46..b1d411e9c 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -4,7 +4,6 @@ using System.Collections.ObjectModel; using System.Linq; using Artemis.Core.LayerBrushes; using Artemis.Core.LayerEffects; -using Artemis.Core.Services; using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile.Abstract; using SkiaSharp; @@ -41,30 +40,27 @@ namespace Artemis.Core _leds = new List(); _expandedPropertyGroups = new List(); - General.PropertyGroupInitialized += GeneralOnPropertyGroupInitialized; + InitializeDefaultGroups(); + + parent.AddChild(this); ApplyRenderElementDefaults(); } internal Layer(Profile profile, ProfileElement parent, LayerEntity layerEntity) { LayerEntity = layerEntity; - EntityId = layerEntity.Id; - Profile = profile; Parent = parent; - Name = layerEntity.Name; - Enabled = layerEntity.Enabled; - Order = layerEntity.Order; General = new LayerGeneralProperties(); Transform = new LayerTransformProperties(); _layerEffects = new List(); _leds = new List(); _expandedPropertyGroups = new List(); - _expandedPropertyGroups.AddRange(layerEntity.ExpandedPropertyGroups); - General.PropertyGroupInitialized += GeneralOnPropertyGroupInitialized; - ApplyRenderElementEntity(); + InitializeDefaultGroups(); + + Load(); } internal LayerEntity LayerEntity { get; set; } @@ -128,25 +124,41 @@ namespace Artemis.Core // Brush first in case it depends on any of the other disposables during it's own disposal _layerBrush?.Dispose(); - _layerBrush = null; foreach (var baseLayerEffect in LayerEffects) baseLayerEffect.Dispose(); - _layerEffects.Clear(); _general?.Dispose(); - _general = null; _layerBitmap?.Dispose(); - _layerBitmap = null; _transform?.Dispose(); - _transform = null; + } - Profile = null; + private void InitializeDefaultGroups() + { + // Layers have two hardcoded property groups, instantiate them + General.Initialize(this, "General.", Constants.CorePluginInfo); + Transform.Initialize(this, "Transform.", Constants.CorePluginInfo); + + General.ShapeType.BaseValueChanged += ShapeTypeOnBaseValueChanged; + ApplyShapeType(); } #region Storage - internal override void ApplyToEntity() + internal override void Load() + { + EntityId = LayerEntity.Id; + Name = LayerEntity.Name; + Enabled = LayerEntity.Enabled; + Order = LayerEntity.Order; + + _expandedPropertyGroups.AddRange(LayerEntity.ExpandedPropertyGroups); + ActivateLayerBrush(); + + LoadRenderElement(); + } + + internal override void Save() { if (_disposed) throw new ObjectDisposedException("Layer"); @@ -181,20 +193,13 @@ namespace Artemis.Core RenderElementEntity.RootDisplayCondition = DisplayConditionGroup?.Entity; DisplayConditionGroup?.ApplyToEntity(); - ApplyRenderElementToEntity(); + SaveRenderElement(); } #endregion #region Shape management - private void GeneralOnPropertyGroupInitialized(object sender, EventArgs e) - { - ApplyShapeType(); - General.ShapeType.BaseValueChanged -= ShapeTypeOnBaseValueChanged; - General.ShapeType.BaseValueChanged += ShapeTypeOnBaseValueChanged; - } - private void ShapeTypeOnBaseValueChanged(object sender, EventArgs e) { ApplyShapeType(); @@ -290,7 +295,7 @@ namespace Artemis.Core baseLayerEffect.Update(delta); } } - + /// public override void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo) { @@ -646,7 +651,51 @@ namespace Artemis.Core #endregion - #region Activation + #region Brush management + + /// + /// Changes the current layer brush to the brush described in the provided + /// + public void ChangeLayerBrush(LayerBrushDescriptor descriptor) + { + if (descriptor == null) + throw new ArgumentNullException(nameof(descriptor)); + + // Ensure the brush reference matches the brush + var current = General.BrushReference.CurrentValue; + if (current.BrushPluginGuid != descriptor.LayerBrushProvider.PluginInfo.Guid || current.BrushType != descriptor.LayerBrushType.Name) + { + General.BrushReference.CurrentValue = new LayerBrushReference + { + BrushPluginGuid = descriptor.LayerBrushProvider.PluginInfo.Guid, + BrushType = descriptor.LayerBrushType.Name + }; + } + + ActivateLayerBrush(); + } + + /// + /// Removes the current layer brush from the layer + /// + public void RemoveLayerBrush() + { + if (LayerBrush == null) + return; + + var brush = LayerBrush; + DeactivateLayerBrush(); + LayerEntity.PropertyEntities.RemoveAll(p => p.PluginGuid == brush.PluginInfo.Guid && p.Path.StartsWith("LayerBrush.")); + } + + internal void ActivateLayerBrush() + { + var current = General.BrushReference.CurrentValue; + var descriptor = LayerBrushStore.Get(current.BrushPluginGuid, current.BrushType)?.LayerBrushDescriptor; + descriptor?.CreateInstance(this); + + OnLayerBrushUpdated(); + } internal void DeactivateLayerBrush() { @@ -656,16 +705,8 @@ namespace Artemis.Core var brush = LayerBrush; LayerBrush = null; brush.Dispose(); - } - internal void RemoveLayerBrush() - { - if (LayerBrush == null) - return; - - var brush = LayerBrush; - DeactivateLayerBrush(); - LayerEntity.PropertyEntities.RemoveAll(p => p.PluginGuid == brush.PluginInfo.Guid && p.Path.StartsWith("LayerBrush.")); + OnLayerBrushUpdated(); } #endregion diff --git a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs index e753cd3d7..b6720b92f 100644 --- a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs +++ b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs @@ -171,14 +171,6 @@ namespace Artemis.Core OnPropertyGroupInitialized(); } - internal void InitializeDataBindings(IDataModelService dataModelService, IDataModelService dataModelService1) - { - foreach (var layerProperty in LayerProperties) - { - layerProperty.InitializeDataBindings(dataModelService); - } - } - internal void ApplyToEntity() { if (!PropertiesInitialized) diff --git a/src/Artemis.Core/Models/Profile/Profile.cs b/src/Artemis.Core/Models/Profile/Profile.cs index 030cf786f..e23874cfe 100644 --- a/src/Artemis.Core/Models/Profile/Profile.cs +++ b/src/Artemis.Core/Models/Profile/Profile.cs @@ -22,7 +22,7 @@ namespace Artemis.Core RedoStack = new Stack(); AddChild(new Folder(this, this, "Root folder")); - ApplyToEntity(); + Save(); } internal Profile(ProfileModule module, ProfileEntity profileEntity) @@ -129,7 +129,7 @@ namespace Artemis.Core _disposed = true; } - internal override void ApplyToEntity() + internal override void Save() { if (_disposed) throw new ObjectDisposedException("Profile"); @@ -140,7 +140,7 @@ namespace Artemis.Core ProfileEntity.IsActive = IsActivated; foreach (var profileElement in Children) - profileElement.ApplyToEntity(); + profileElement.Save(); ProfileEntity.Folders.Clear(); ProfileEntity.Folders.AddRange(GetAllFolders().Select(f => f.FolderEntity)); diff --git a/src/Artemis.Core/Models/Profile/ProfileElement.cs b/src/Artemis.Core/Models/Profile/ProfileElement.cs index efa5bdb52..0a2ee39aa 100644 --- a/src/Artemis.Core/Models/Profile/ProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/ProfileElement.cs @@ -23,18 +23,27 @@ namespace Artemis.Core ChildrenList = new List(); } + /// + /// Gets the unique ID of this profile element + /// public Guid EntityId { get => _entityId; internal set => SetAndNotify(ref _entityId, value); } + /// + /// Gets the profile this element belongs to + /// public Profile Profile { get => _profile; internal set => SetAndNotify(ref _profile, value); } + /// + /// Gets the parent of this element + /// public ProfileElement Parent { get => _parent; @@ -73,12 +82,6 @@ namespace Artemis.Core set => SetAndNotify(ref _enabled, value); } - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - /// /// Updates the element /// @@ -90,39 +93,13 @@ namespace Artemis.Core /// public abstract void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo); - public List GetAllFolders() + /// + public override string ToString() { - if (_disposed) - throw new ObjectDisposedException(GetType().Name); - - var folders = new List(); - foreach (var childFolder in Children.Where(c => c is Folder).Cast()) - { - // Add all folders in this element - folders.Add(childFolder); - // Add all folders in folders inside this element - folders.AddRange(childFolder.GetAllFolders()); - } - - return folders; + return $"{nameof(EntityId)}: {EntityId}, {nameof(Order)}: {Order}, {nameof(Name)}: {Name}"; } - public List GetAllLayers() - { - if (_disposed) - throw new ObjectDisposedException(GetType().Name); - - var layers = new List(); - - // Add all layers in this element - layers.AddRange(Children.Where(c => c is Layer).Cast()); - - // Add all layers in folders inside this element - foreach (var childFolder in Children.Where(c => c is Folder).Cast()) - layers.AddRange(childFolder.GetAllLayers()); - - return layers; - } + #region Hierarchy /// /// Adds a profile element to the collection, optionally at the given position (1-based) @@ -189,9 +166,64 @@ namespace Artemis.Core OnChildRemoved(); } - public override string ToString() + /// + /// Returns a flattened list of all child folders + /// + /// + public List GetAllFolders() { - return $"{nameof(EntityId)}: {EntityId}, {nameof(Order)}: {Order}, {nameof(Name)}: {Name}"; + if (_disposed) + throw new ObjectDisposedException(GetType().Name); + + var folders = new List(); + foreach (var childFolder in Children.Where(c => c is Folder).Cast()) + { + // Add all folders in this element + folders.Add(childFolder); + // Add all folders in folders inside this element + folders.AddRange(childFolder.GetAllFolders()); + } + + return folders; + } + + /// + /// Returns a flattened list of all child layers + /// + /// + public List GetAllLayers() + { + if (_disposed) + throw new ObjectDisposedException(GetType().Name); + + var layers = new List(); + + // Add all layers in this element + layers.AddRange(Children.Where(c => c is Layer).Cast()); + + // Add all layers in folders inside this element + foreach (var childFolder in Children.Where(c => c is Folder).Cast()) + layers.AddRange(childFolder.GetAllLayers()); + + return layers; + } + + #endregion + + #region Storage + + internal abstract void Load(); + internal abstract void Save(); + + #endregion + + #region IDisposable + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) @@ -201,10 +233,7 @@ namespace Artemis.Core } } - /// - /// Applies the profile element's properties to the underlying storage entity - /// - internal abstract void ApplyToEntity(); + #endregion #region Events diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs index a75ed2b56..bc3d86cb6 100644 --- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs @@ -12,21 +12,23 @@ namespace Artemis.Core { public abstract class RenderProfileElement : ProfileElement { - protected void ApplyRenderElementDefaults() + internal void ApplyRenderElementDefaults() { MainSegmentLength = TimeSpan.FromSeconds(5); } - protected void ApplyRenderElementEntity() + internal void LoadRenderElement() { StartSegmentLength = RenderElementEntity.StartSegmentLength; MainSegmentLength = RenderElementEntity.MainSegmentLength; EndSegmentLength = RenderElementEntity.EndSegmentLength; DisplayContinuously = RenderElementEntity.DisplayContinuously; AlwaysFinishTimeline = RenderElementEntity.AlwaysFinishTimeline; + + ActivateEffects(); } - protected void ApplyRenderElementToEntity() + internal void SaveRenderElement() { RenderElementEntity.StartSegmentLength = StartSegmentLength; RenderElementEntity.MainSegmentLength = MainSegmentLength; @@ -206,7 +208,7 @@ namespace Artemis.Core #endregion - #region Effects + #region Effect management protected List _layerEffects; @@ -215,11 +217,35 @@ namespace Artemis.Core /// public ReadOnlyCollection LayerEffects => _layerEffects.AsReadOnly(); - internal void RemoveLayerEffect([NotNull] BaseLayerEffect effect) + /// + /// Adds a the layer effect described inthe provided + /// + public void AddLayerEffect(LayerEffectDescriptor descriptor) + { + if (descriptor == null) + throw new ArgumentNullException(nameof(descriptor)); + + var entity = new LayerEffectEntity + { + Id = Guid.NewGuid(), + Enabled = true, + Order = LayerEffects.Count + 1 + }; + descriptor.CreateInstance(this, entity); + OnLayerEffectsUpdated(); + } + + /// + /// Removes the provided layer + /// + /// + public void RemoveLayerEffect([NotNull] BaseLayerEffect effect) { if (effect == null) throw new ArgumentNullException(nameof(effect)); - DeactivateLayerEffect(effect); + // Remove the effect from the layer and dispose it + _layerEffects.Remove(effect); + effect.Dispose(); // Update the order on the remaining effects var index = 0; @@ -232,20 +258,31 @@ namespace Artemis.Core OnLayerEffectsUpdated(); } - internal void AddLayerEffect([NotNull] BaseLayerEffect effect) + internal void ActivateEffects() { - if (effect == null) throw new ArgumentNullException(nameof(effect)); - _layerEffects.Add(effect); - OnLayerEffectsUpdated(); + foreach (var layerEffectEntity in RenderElementEntity.LayerEffects) + { + if (_layerEffects.Any(e => e.EntityId == layerEffectEntity.Id)) + continue; + + var descriptor = LayerEffectStore.Get(layerEffectEntity.PluginGuid, layerEffectEntity.EffectType)?.LayerEffectDescriptor; + descriptor?.CreateInstance(this, layerEffectEntity); + } } - internal void DeactivateLayerEffect([NotNull] BaseLayerEffect effect) + internal void ActivateLayerEffect(BaseLayerEffect layerEffect) { - if (effect == null) throw new ArgumentNullException(nameof(effect)); + _layerEffects.Add(layerEffect); - // Remove the effect from the layer and dispose it - _layerEffects.Remove(effect); - effect.Dispose(); + // Update the order on the effects + var index = 0; + foreach (var baseLayerEffect in LayerEffects.OrderBy(e => e.Order)) + { + baseLayerEffect.Order = Order = index + 1; + index++; + } + + OnLayerEffectsUpdated(); } #endregion @@ -296,6 +333,5 @@ namespace Artemis.Core } #endregion - } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs b/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs index 9edd2a6cc..e0fbc4f35 100644 --- a/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs +++ b/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs @@ -22,6 +22,12 @@ namespace Artemis.Core.DataModelExpansions [DataModelIgnore] public DataModelPropertyAttribute DataModelDescription { get; internal set; } + /// + /// Gets the is expansion status indicating whether this data model expands the main data model + /// + [DataModelIgnore] + public bool IsExpansion { get; internal set; } + public bool ContainsPath(string path) { var parts = path.Split('.'); diff --git a/src/Artemis.Core/Plugins/DataModelExpansions/DataModelExpansion.cs b/src/Artemis.Core/Plugins/DataModelExpansions/DataModelExpansion.cs index 8230e08f8..28946a89b 100644 --- a/src/Artemis.Core/Plugins/DataModelExpansions/DataModelExpansion.cs +++ b/src/Artemis.Core/Plugins/DataModelExpansions/DataModelExpansion.cs @@ -10,7 +10,8 @@ namespace Artemis.Core.DataModelExpansions public abstract class DataModelExpansion : BaseDataModelExpansion where T : DataModel { /// - /// The data model driving this module + /// The main data model of this data model expansion + /// Note: This default data model is automatically registered upon plugin enable /// public T DataModel { diff --git a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushDescriptor.cs b/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushDescriptor.cs index 685435646..a4173e75c 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushDescriptor.cs +++ b/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushDescriptor.cs @@ -1,4 +1,5 @@ using System; +using Ninject; namespace Artemis.Core.LayerBrushes { @@ -41,5 +42,28 @@ namespace Artemis.Core.LayerBrushes /// The plugin that provided this /// public LayerBrushProvider LayerBrushProvider { get; } + + /// + /// Gets or sets the kernel used to instantiate the described layer brush + /// + internal IKernel Kernel { get; set; } + + /// + /// Creates an instance of the described brush and applies it to the layer + /// + internal void CreateInstance(Layer layer) + { + if (layer.LayerBrush != null) + throw new ArtemisCoreException("Layer already has an instantiated layer brush"); + + var brush = (BaseLayerBrush) Kernel.Get(LayerBrushType); + brush.Layer = layer; + brush.Descriptor = this; + brush.Initialize(); + brush.Update(0); + + layer.LayerBrush = brush; + layer.OnLayerBrushUpdated(); + } } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs b/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs index b3c1db3b5..47d95d396 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs +++ b/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs @@ -1,4 +1,8 @@ using System; +using System.Linq; +using Artemis.Core.LayerBrushes; +using Artemis.Storage.Entities.Profile; +using Ninject; namespace Artemis.Core.LayerEffects { @@ -41,5 +45,33 @@ namespace Artemis.Core.LayerEffects /// The plugin that provided this /// public LayerEffectProvider LayerEffectProvider { get; } + + /// + /// Gets or sets the kernel used to instantiate the described layer effect + /// + internal IKernel Kernel { get; set; } + + /// + /// Creates an instance of the described effect and applies it to the render element + /// + internal void CreateInstance(RenderProfileElement renderElement, LayerEffectEntity entity) + { + // Skip effects already on the element + if (renderElement.LayerEffects.Any(e => e.EntityId == entity.Id)) + return; + + var effect = (BaseLayerEffect) Kernel.Get(LayerEffectType); + effect.ProfileElement = renderElement; + effect.EntityId = entity.Id; + effect.Order = entity.Order; + effect.Name = entity.Name; + effect.Enabled = entity.Enabled; + effect.Descriptor = this; + + effect.Initialize(); + effect.Update(0); + + renderElement.ActivateLayerEffect(effect); + } } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Modules/Module.cs b/src/Artemis.Core/Plugins/Modules/Module.cs index 31efee688..e67eb1174 100644 --- a/src/Artemis.Core/Plugins/Modules/Module.cs +++ b/src/Artemis.Core/Plugins/Modules/Module.cs @@ -14,6 +14,7 @@ namespace Artemis.Core.Modules { /// /// The data model driving this module + /// Note: This default data model is automatically registered upon plugin enable /// public T DataModel { diff --git a/src/Artemis.Core/Plugins/Modules/ProfileModule.cs b/src/Artemis.Core/Plugins/Modules/ProfileModule.cs index 9b7c303ff..86afbcc0a 100644 --- a/src/Artemis.Core/Plugins/Modules/ProfileModule.cs +++ b/src/Artemis.Core/Plugins/Modules/ProfileModule.cs @@ -18,6 +18,7 @@ namespace Artemis.Core.Modules { /// /// The data model driving this module + /// Note: This default data model is automatically registered upon plugin enable /// public T DataModel { @@ -118,7 +119,7 @@ namespace Artemis.Core.Modules /// Indicates whether or not a profile change is being animated /// public bool AnimatingProfileChange { get; private set; } - + /// /// Called after the profile has updated /// diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index 1c739c425..eaf195c28 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -9,6 +9,7 @@ using Artemis.Core.JsonConverters; using Artemis.Core.Ninject; using Artemis.Storage; using Newtonsoft.Json; +using Ninject; using RGB.NET.Core; using Serilog; using Serilog.Events; @@ -23,6 +24,7 @@ namespace Artemis.Core.Services internal class CoreService : ICoreService { private readonly Stopwatch _frameStopWatch; + private readonly IKernel _kernel; private readonly ILogger _logger; private readonly PluginSetting _loggingLevel; private readonly IPluginService _pluginService; @@ -34,9 +36,10 @@ namespace Artemis.Core.Services private List _modules; // ReSharper disable once UnusedParameter.Local - Storage migration service is injected early to ensure it runs before anything else - public CoreService(ILogger logger, StorageMigrationService _, ISettingsService settingsService, IPluginService pluginService, - IRgbService rgbService, ISurfaceService surfaceService, IProfileService profileService, IModuleService moduleService) + public CoreService(IKernel kernel, ILogger logger, StorageMigrationService _, ISettingsService settingsService, IPluginService pluginService, + IRgbService rgbService, ISurfaceService surfaceService, IProfileService profileService) { + _kernel = kernel; _logger = logger; _pluginService = pluginService; _rgbService = rgbService; @@ -77,6 +80,8 @@ namespace Artemis.Core.Services _logger.Information("Initializing Artemis Core version {version}", versionAttribute?.InformationalVersion); ApplyLoggingLevel(); + DeserializationLogger.Initialize(_kernel); + // Initialize the services _pluginService.CopyBuiltInPlugins(); _pluginService.LoadPlugins(StartupArguments.Contains("--ignore-plugin-lock")); diff --git a/src/Artemis.Core/Services/DataBindingService.cs b/src/Artemis.Core/Services/DataBindingService.cs deleted file mode 100644 index 3af46569f..000000000 --- a/src/Artemis.Core/Services/DataBindingService.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Newtonsoft.Json; -using Serilog; - -namespace Artemis.Core.Services -{ - internal class DataBindingService : IDataBindingService - { - private readonly ILogger _logger; - private readonly List _registeredDataBindingModifierTypes; - - public DataBindingService(ILogger logger) - { - _logger = logger; - _registeredDataBindingModifierTypes = new List(); - - RegisterBuiltInModifiers(); - } - - public IReadOnlyCollection RegisteredDataBindingModifierTypes - { - get - { - lock (_registeredDataBindingModifierTypes) - { - return _registeredDataBindingModifierTypes.AsReadOnly(); - } - } - } - - public void RegisterModifierType(PluginInfo pluginInfo, DataBindingModifierType dataBindingModifierType) - { - if (pluginInfo == null) - throw new ArgumentNullException(nameof(pluginInfo)); - if (dataBindingModifierType == null) - throw new ArgumentNullException(nameof(dataBindingModifierType)); - - lock (_registeredDataBindingModifierTypes) - { - if (_registeredDataBindingModifierTypes.Contains(dataBindingModifierType)) - return; - - dataBindingModifierType.Register(pluginInfo, this); - _registeredDataBindingModifierTypes.Add(dataBindingModifierType); - } - } - - public void RemoveModifierType(DataBindingModifierType dataBindingModifierType) - { - if (dataBindingModifierType == null) - throw new ArgumentNullException(nameof(dataBindingModifierType)); - - lock (_registeredDataBindingModifierTypes) - { - if (!_registeredDataBindingModifierTypes.Contains(dataBindingModifierType)) - return; - - dataBindingModifierType.Unsubscribe(); - _registeredDataBindingModifierTypes.Remove(dataBindingModifierType); - } - } - - public List GetCompatibleModifierTypes(Type type) - { - lock (_registeredDataBindingModifierTypes) - { - if (type == null) - return new List(_registeredDataBindingModifierTypes); - - var candidates = _registeredDataBindingModifierTypes.Where(c => c.CompatibleTypes.Any(t => t.IsCastableFrom(type))).ToList(); - - // If there are multiple modifier types with the same description, use the closest match - foreach (var dataBindingModifierTypes in candidates.GroupBy(c => c.Description).Where(g => g.Count() > 1).ToList()) - { - var bestCandidate = dataBindingModifierTypes.OrderByDescending(c => c.CompatibleTypes.Contains(type)).FirstOrDefault(); - foreach (var dataBindingModifierType in dataBindingModifierTypes) - { - if (dataBindingModifierType != bestCandidate) - candidates.Remove(dataBindingModifierType); - } - } - - return candidates; - } - } - - public DataBindingModifierType GetModifierType(Guid modifierTypePluginGuid, string modifierType) - { - return RegisteredDataBindingModifierTypes.FirstOrDefault(o => o.PluginInfo.Guid == modifierTypePluginGuid && o.GetType().Name == modifierType); - } - - public void LogModifierDeserializationFailure(string modifierName, JsonSerializationException exception) - { - _logger.Warning(exception, "Failed to deserialize static parameter for modifier {modifierName}", modifierName); - } - - private void RegisterBuiltInModifiers() - { - RegisterModifierType(Constants.CorePluginInfo, new MultiplicationModifierType()); - RegisterModifierType(Constants.CorePluginInfo, new DivideModifierType()); - RegisterModifierType(Constants.CorePluginInfo, new FloorModifierType()); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Services/DataModelService.cs b/src/Artemis.Core/Services/DataModelService.cs deleted file mode 100644 index 8d5d8956d..000000000 --- a/src/Artemis.Core/Services/DataModelService.cs +++ /dev/null @@ -1,252 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Artemis.Core.DataModelExpansions; -using Artemis.Core.Modules; -using Newtonsoft.Json; -using Serilog; - -namespace Artemis.Core.Services -{ - /// - /// Provides access to the main data model - /// - internal class DataModelService : IDataModelService - { - private readonly List _dataModelExpansions; - private readonly ILogger _logger; - private readonly IPluginService _pluginService; - private readonly List _registeredConditionOperators; - - public DataModelService(IPluginService pluginService, ILogger logger) - { - _pluginService = pluginService; - _logger = logger; - _dataModelExpansions = new List(); - _registeredConditionOperators = new List(); - - _pluginService.PluginEnabled += PluginServiceOnPluginEnabled; - _pluginService.PluginDisabled += PluginServiceOnPluginDisabled; - - RegisterBuiltInConditionOperators(); - - foreach (var module in _pluginService.GetPluginsOfType().Where(m => m.InternalExpandsMainDataModel)) - AddModuleDataModel(module); - foreach (var dataModelExpansion in _pluginService.GetPluginsOfType()) - AddDataModelExpansionDataModel(dataModelExpansion); - } - - public IReadOnlyCollection RegisteredConditionOperators - { - get - { - lock (_registeredConditionOperators) - { - return _registeredConditionOperators.AsReadOnly(); - } - } - } - - public IReadOnlyCollection DataModelExpansions - { - get - { - lock (_dataModelExpansions) - { - return new List(_dataModelExpansions).AsReadOnly(); - } - } - } - - public void AddExpansion(DataModel dataModelExpansion) - { - lock (_dataModelExpansions) - { - _dataModelExpansions.Add(dataModelExpansion); - // TODO SpoinkyNL 3-3-2018: Initialize the expansion and fire an event - } - } - - public void RemoveExpansion(DataModel dataModelExpansion) - { - lock (_dataModelExpansions) - { - if (!_dataModelExpansions.Contains(dataModelExpansion)) - throw new ArtemisCoreException("Cannot remove a data model expansion that wasn't previously added."); - - // TODO SpoinkyNL 3-3-2018: Dispose the expansion and fire an event - _dataModelExpansions.Remove(dataModelExpansion); - } - } - - public DataModel GetPluginDataModel(Plugin plugin) - { - if (plugin is Module module) - return module.InternalDataModel; - if (plugin is BaseDataModelExpansion dataModelExpansion) - return dataModelExpansion.InternalDataModel; - return null; - } - - public DataModel GetPluginDataModelByGuid(Guid pluginGuid) - { - var pluginInfo = _pluginService.GetAllPluginInfo().FirstOrDefault(i => i.Guid == pluginGuid); - if (pluginInfo == null || !pluginInfo.Enabled) - return null; - - return GetPluginDataModel(pluginInfo.Instance); - } - - public bool GetPluginExtendsDataModel(Plugin plugin) - { - if (plugin is Module module) - return module.InternalExpandsMainDataModel; - if (plugin is BaseDataModelExpansion) - return true; - - return false; - } - - - public void RegisterConditionOperator(PluginInfo pluginInfo, DisplayConditionOperator displayConditionOperator) - { - if (pluginInfo == null) - throw new ArgumentNullException(nameof(pluginInfo)); - if (displayConditionOperator == null) - throw new ArgumentNullException(nameof(displayConditionOperator)); - - lock (_registeredConditionOperators) - { - if (_registeredConditionOperators.Contains(displayConditionOperator)) - return; - - displayConditionOperator.Register(pluginInfo, this); - _registeredConditionOperators.Add(displayConditionOperator); - } - } - - public void RemoveConditionOperator(DisplayConditionOperator displayConditionOperator) - { - if (displayConditionOperator == null) - throw new ArgumentNullException(nameof(displayConditionOperator)); - - lock (_registeredConditionOperators) - { - if (!_registeredConditionOperators.Contains(displayConditionOperator)) - return; - - displayConditionOperator.Unsubscribe(); - _registeredConditionOperators.Remove(displayConditionOperator); - } - } - - public List GetCompatibleConditionOperators(Type type) - { - lock (_registeredConditionOperators) - { - if (type == null) - return new List(_registeredConditionOperators); - - var candidates = _registeredConditionOperators.Where(c => c.CompatibleTypes.Any(t => t.IsCastableFrom(type))).ToList(); - - // If there are multiple operators with the same description, use the closest match - foreach (var displayConditionOperators in candidates.GroupBy(c => c.Description).Where(g => g.Count() > 1).ToList()) - { - var bestCandidate = displayConditionOperators.OrderByDescending(c => c.CompatibleTypes.Contains(type)).FirstOrDefault(); - foreach (var displayConditionOperator in displayConditionOperators) - { - if (displayConditionOperator != bestCandidate) - candidates.Remove(displayConditionOperator); - } - } - - return candidates; - } - } - - public DisplayConditionOperator GetConditionOperator(Guid operatorPluginGuid, string operatorType) - { - return RegisteredConditionOperators.FirstOrDefault(o => o.PluginInfo.Guid == operatorPluginGuid && o.GetType().Name == operatorType); - } - - public void LogPredicateDeserializationFailure(DisplayConditionPredicate displayConditionPredicate, JsonException exception) - { - _logger.Warning( - exception, - "Failed to deserialize display condition predicate {left} {operator} {right}", - displayConditionPredicate.Entity.LeftPropertyPath, - displayConditionPredicate.Entity.OperatorType, - displayConditionPredicate.Entity.RightPropertyPath - ); - } - - public void LogListPredicateDeserializationFailure(DisplayConditionListPredicate displayConditionPredicate, JsonException exception) - { - _logger.Warning( - exception, - "Failed to deserialize display condition list predicate {list} => {left} {operator} {right}", - displayConditionPredicate.Entity.ListPropertyPath, - displayConditionPredicate.Entity.LeftPropertyPath, - displayConditionPredicate.Entity.OperatorType, - displayConditionPredicate.Entity.RightPropertyPath - ); - } - - private void RegisterBuiltInConditionOperators() - { - // General usage for any type - RegisterConditionOperator(Constants.CorePluginInfo, new EqualsConditionOperator()); - RegisterConditionOperator(Constants.CorePluginInfo, new NotEqualConditionOperator()); - - // Numeric operators - RegisterConditionOperator(Constants.CorePluginInfo, new LessThanConditionOperator()); - RegisterConditionOperator(Constants.CorePluginInfo, new GreaterThanConditionOperator()); - RegisterConditionOperator(Constants.CorePluginInfo, new LessThanOrEqualConditionOperator()); - RegisterConditionOperator(Constants.CorePluginInfo, new GreaterThanOrEqualConditionOperator()); - - // String operators - RegisterConditionOperator(Constants.CorePluginInfo, new StringEqualsConditionOperator()); - RegisterConditionOperator(Constants.CorePluginInfo, new StringNotEqualConditionOperator()); - RegisterConditionOperator(Constants.CorePluginInfo, new StringContainsConditionOperator()); - RegisterConditionOperator(Constants.CorePluginInfo, new StringNotContainsConditionOperator()); - RegisterConditionOperator(Constants.CorePluginInfo, new StringStartsWithConditionOperator()); - RegisterConditionOperator(Constants.CorePluginInfo, new StringEndsWithConditionOperator()); - RegisterConditionOperator(Constants.CorePluginInfo, new StringNullConditionOperator()); - } - - private void PluginServiceOnPluginEnabled(object sender, PluginEventArgs e) - { - if (e.PluginInfo.Instance is Module module && module.InternalExpandsMainDataModel) - AddModuleDataModel(module); - else if (e.PluginInfo.Instance is BaseDataModelExpansion dataModelExpansion) - AddDataModelExpansionDataModel(dataModelExpansion); - } - - private void AddDataModelExpansionDataModel(BaseDataModelExpansion dataModelExpansion) - { - if (dataModelExpansion.InternalDataModel.DataModelDescription == null) - throw new ArtemisPluginException(dataModelExpansion.PluginInfo, "Data model expansion overrides GetDataModelDescription but returned null"); - - AddExpansion(dataModelExpansion.InternalDataModel); - } - - private void AddModuleDataModel(Module module) - { - if (module.InternalDataModel.DataModelDescription == null) - throw new ArtemisPluginException(module.PluginInfo, "Module overrides GetDataModelDescription but returned null"); - - AddExpansion(module.InternalDataModel); - } - - private void PluginServiceOnPluginDisabled(object sender, PluginEventArgs e) - { - // Remove all data models related to the plugin - lock (_dataModelExpansions) - { - var toRemove = _dataModelExpansions.Where(d => d.PluginInfo == e.PluginInfo).ToList(); - foreach (var dataModel in toRemove) - _dataModelExpansions.Remove(dataModel); - } - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Services/Interfaces/ICoreService.cs b/src/Artemis.Core/Services/Interfaces/ICoreService.cs index 674db94ef..b506f18ac 100644 --- a/src/Artemis.Core/Services/Interfaces/ICoreService.cs +++ b/src/Artemis.Core/Services/Interfaces/ICoreService.cs @@ -3,6 +3,9 @@ using System.Collections.Generic; namespace Artemis.Core.Services { + /// + /// A service that initializes the Core and manages the render loop + /// public interface ICoreService : IArtemisService, IDisposable { /// diff --git a/src/Artemis.Core/Services/Interfaces/IDataModelService.cs b/src/Artemis.Core/Services/Interfaces/IDataModelService.cs deleted file mode 100644 index 7b2cb5582..000000000 --- a/src/Artemis.Core/Services/Interfaces/IDataModelService.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using System.Collections.Generic; -using Artemis.Core.DataModelExpansions; -using Artemis.Core.Properties; -using Newtonsoft.Json; - -namespace Artemis.Core.Services -{ - public interface IDataModelService : IArtemisService - { - /// - /// Gets a read-only collection of all registered condition operators - /// - IReadOnlyCollection RegisteredConditionOperators { get; } - - /// - /// Gets a read-only collection of all registered data model expansions - /// - IReadOnlyCollection DataModelExpansions { get; } - - /// - /// Add an expansion to the datamodel to be available for use after the next update - /// - /// - void AddExpansion(DataModel baseDataModelExpansion); - - /// - /// Remove a previously added expansion so that it is no longer available and updated - /// - /// - void RemoveExpansion(DataModel baseDataModelExpansion); - - /// - /// If found, returns the data model of the provided plugin - /// - /// Should be a module with a data model or a data model expansion - DataModel GetPluginDataModel(Plugin plugin); - - /// - /// If found, returns the data model of the provided plugin - /// - /// Should be a module with a data model or a data model expansion - DataModel GetPluginDataModelByGuid(Guid pluginGuid); - - /// - /// Determines whether the given plugin expands the main data model - /// - /// - /// - bool GetPluginExtendsDataModel(Plugin plugin); - - /// - /// Registers a new condition operator for use in layer conditions - /// - /// The PluginInfo of the plugin this condition operator belongs to - /// The condition operator to register - void RegisterConditionOperator([NotNull] PluginInfo pluginInfo, [NotNull] DisplayConditionOperator displayConditionOperator); - - /// - /// Removes a condition operator so it is no longer available for use in layer conditions - /// - /// The layer condition operator to remove - void RemoveConditionOperator([NotNull] DisplayConditionOperator displayConditionOperator); - - /// - /// Returns all the display condition operators compatible with the provided type - /// - List GetCompatibleConditionOperators(Type type); - - /// - /// Gets a condition operator by its plugin GUID and type name - /// - /// The operator's plugin GUID - /// The type name of the operator - DisplayConditionOperator GetConditionOperator(Guid operatorPluginGuid, string operatorType); - - /// - /// Logs a predicate deserialization failure - /// - /// The predicate that failed to deserialize - /// The JSON exception that occurred - void LogPredicateDeserializationFailure(DisplayConditionPredicate displayConditionPredicate, JsonException exception); - - /// - /// Logs a list predicate deserialization failure - /// - /// The list predicate that failed to deserialize - /// The JSON exception that occurred - void LogListPredicateDeserializationFailure(DisplayConditionListPredicate displayConditionListPredicate, JsonException exception); - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Services/Interfaces/IDeviceService.cs b/src/Artemis.Core/Services/Interfaces/IDeviceService.cs index cb09e83e7..154ad5466 100644 --- a/src/Artemis.Core/Services/Interfaces/IDeviceService.cs +++ b/src/Artemis.Core/Services/Interfaces/IDeviceService.cs @@ -1,5 +1,8 @@ namespace Artemis.Core.Services { + /// + /// A service that allows you manage an + /// public interface IDeviceService : IArtemisService { /// diff --git a/src/Artemis.Core/Services/Interfaces/IRenderElementService.cs b/src/Artemis.Core/Services/Interfaces/IRenderElementService.cs deleted file mode 100644 index 79319f578..000000000 --- a/src/Artemis.Core/Services/Interfaces/IRenderElementService.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Artemis.Core.LayerBrushes; -using Artemis.Core.LayerEffects; - -namespace Artemis.Core.Services -{ - public interface IRenderElementService : IArtemisService - { - /// - /// Creates a new layer - /// - /// - /// - /// - /// - Layer CreateLayer(Profile profile, ProfileElement parent, string name); - - /// - /// Removes the currently active layer brush from the and deletes any settings - /// - /// The layer to remove the active brush from - void RemoveLayerBrush(Layer layer); - - /// - /// Deactivates the currently active layer brush from the but keeps all settings - /// - /// The layer to deactivate the active brush on - void DeactivateLayerBrush(Layer layer); - - /// - /// Instantiates and adds the described by the provided - /// - /// to the . - /// - /// The layer to instantiate the brush for - /// - BaseLayerBrush InstantiateLayerBrush(Layer layer); - - /// - /// Instantiates and adds the described by the provided - /// to the . - /// - /// The layer/folder to instantiate the effect for - void InstantiateLayerEffects(RenderProfileElement renderProfileElement); - - /// - /// Adds the described by the provided to the - /// . - /// - /// The layer/folder to instantiate the effect for - /// - /// - BaseLayerEffect AddLayerEffect(RenderProfileElement renderProfileElement, LayerEffectDescriptor layerEffectDescriptor); - - void RemoveLayerEffect(BaseLayerEffect layerEffect); - - void InstantiateDisplayConditions(RenderProfileElement renderElement); - void InstantiateDataBindings(RenderProfileElement renderElement); - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Services/Interfaces/IRgbService.cs b/src/Artemis.Core/Services/Interfaces/IRgbService.cs index 5b78f6bfb..e5cd0c213 100644 --- a/src/Artemis.Core/Services/Interfaces/IRgbService.cs +++ b/src/Artemis.Core/Services/Interfaces/IRgbService.cs @@ -4,7 +4,10 @@ using RGB.NET.Core; namespace Artemis.Core.Services { - public interface IRgbService : IArtemisService + /// + /// A service that allows you to manage the and its contents + /// + public interface IRgbService : IArtemisService, IDisposable { /// /// Gets or sets the RGB surface rendering is performed on @@ -26,7 +29,14 @@ namespace Artemis.Core.Services /// IReadOnlyCollection LoadedDevices { get; } + /// + /// Gets the update trigger that drives the render loop + /// TimerUpdateTrigger UpdateTrigger { get; } + + /// + /// Gets or sets whether rendering should be paused + /// bool IsRenderPaused { get; set; } /// @@ -35,8 +45,6 @@ namespace Artemis.Core.Services /// void AddDeviceProvider(IRGBDeviceProvider deviceProvider); - void Dispose(); - /// /// Occurs when a single device has loaded /// @@ -47,6 +55,9 @@ namespace Artemis.Core.Services /// event EventHandler DeviceReloaded; + /// + /// Recalculates the LED group used by the + /// void UpdateSurfaceLedGroup(); } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Registration/ConditionOperatorService.cs b/src/Artemis.Core/Services/Registration/ConditionOperatorService.cs new file mode 100644 index 000000000..9ea6fab80 --- /dev/null +++ b/src/Artemis.Core/Services/Registration/ConditionOperatorService.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Artemis.Core.Services +{ + internal class ConditionOperatorService : IConditionOperatorService + { + public ConditionOperatorService() + { + RegisterBuiltInConditionOperators(); + } + + public ConditionOperatorRegistration RegisterConditionOperator(PluginInfo pluginInfo, ConditionOperator conditionOperator) + { + if (pluginInfo == null) + throw new ArgumentNullException(nameof(pluginInfo)); + if (conditionOperator == null) + throw new ArgumentNullException(nameof(conditionOperator)); + + conditionOperator.PluginInfo = pluginInfo; + return ConditionOperatorStore.Add(conditionOperator); + } + + public void RemoveConditionOperator(ConditionOperatorRegistration registration) + { + if (registration == null) + throw new ArgumentNullException(nameof(registration)); + ConditionOperatorStore.Remove(registration); + } + + public List GetConditionOperatorsForType(Type type) + { + return ConditionOperatorStore.GetForType(type).Select(r => r.ConditionOperator).ToList(); + } + + public ConditionOperator GetConditionOperator(Guid operatorPluginGuid, string operatorType) + { + return ConditionOperatorStore.Get(operatorPluginGuid, operatorType)?.ConditionOperator; + } + + private void RegisterBuiltInConditionOperators() + { + // General usage for any type + RegisterConditionOperator(Constants.CorePluginInfo, new EqualsConditionOperator()); + RegisterConditionOperator(Constants.CorePluginInfo, new NotEqualConditionOperator()); + + // Numeric operators + RegisterConditionOperator(Constants.CorePluginInfo, new LessThanConditionOperator()); + RegisterConditionOperator(Constants.CorePluginInfo, new GreaterThanConditionOperator()); + RegisterConditionOperator(Constants.CorePluginInfo, new LessThanOrEqualConditionOperator()); + RegisterConditionOperator(Constants.CorePluginInfo, new GreaterThanOrEqualConditionOperator()); + + // String operators + RegisterConditionOperator(Constants.CorePluginInfo, new StringEqualsConditionOperator()); + RegisterConditionOperator(Constants.CorePluginInfo, new StringNotEqualConditionOperator()); + RegisterConditionOperator(Constants.CorePluginInfo, new StringContainsConditionOperator()); + RegisterConditionOperator(Constants.CorePluginInfo, new StringNotContainsConditionOperator()); + RegisterConditionOperator(Constants.CorePluginInfo, new StringStartsWithConditionOperator()); + RegisterConditionOperator(Constants.CorePluginInfo, new StringEndsWithConditionOperator()); + RegisterConditionOperator(Constants.CorePluginInfo, new StringNullConditionOperator()); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/Registration/DataBindingService.cs b/src/Artemis.Core/Services/Registration/DataBindingService.cs new file mode 100644 index 000000000..8deed0c63 --- /dev/null +++ b/src/Artemis.Core/Services/Registration/DataBindingService.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Artemis.Core.Services +{ + internal class DataBindingService : IDataBindingService + { + public DataBindingService() + { + RegisterBuiltInModifiers(); + } + + public DataBindingModifierTypeRegistration RegisterModifierType(PluginInfo pluginInfo, DataBindingModifierType dataBindingModifierType) + { + if (pluginInfo == null) + throw new ArgumentNullException(nameof(pluginInfo)); + if (dataBindingModifierType == null) + throw new ArgumentNullException(nameof(dataBindingModifierType)); + + dataBindingModifierType.PluginInfo = pluginInfo; + return DataBindingModifierTypeStore.Add(dataBindingModifierType); + } + + public void RemoveModifierType(DataBindingModifierTypeRegistration registration) + { + if (registration == null) + throw new ArgumentNullException(nameof(registration)); + DataBindingModifierTypeStore.Remove(registration); + } + + public List GetCompatibleModifierTypes(Type type) + { + return DataBindingModifierTypeStore.GetForType(type).Select(r => r.DataBindingModifierType).ToList(); + } + + public DataBindingModifierType GetModifierType(Guid modifierTypePluginGuid, string modifierType) + { + return DataBindingModifierTypeStore.Get(modifierTypePluginGuid, modifierType)?.DataBindingModifierType; + } + + private void RegisterBuiltInModifiers() + { + RegisterModifierType(Constants.CorePluginInfo, new MultiplicationModifierType()); + RegisterModifierType(Constants.CorePluginInfo, new DivideModifierType()); + RegisterModifierType(Constants.CorePluginInfo, new FloorModifierType()); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/Registration/DataModelService.cs b/src/Artemis.Core/Services/Registration/DataModelService.cs new file mode 100644 index 000000000..f3dcfaeb9 --- /dev/null +++ b/src/Artemis.Core/Services/Registration/DataModelService.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; + +namespace Artemis.Core.Services +{ + internal class DataModelService : IDataModelService + { + public DataModelService(IPluginService pluginService) + { + // Add data models of already loaded plugins + foreach (var module in pluginService.GetPluginsOfType()) + AddModuleDataModel(module); + foreach (var dataModelExpansion in pluginService.GetPluginsOfType()) + AddDataModelExpansionDataModel(dataModelExpansion); + + // Add data models of new plugins when they get enabled + pluginService.PluginEnabled += PluginServiceOnPluginEnabled; + } + + public DataModelRegistration RegisterDataModel(DataModel dataModel) + { + if (dataModel == null) + throw new ArgumentNullException(nameof(dataModel)); + return DataModelStore.Add(dataModel); + } + + public void RemoveDataModel(DataModelRegistration registration) + { + if (registration == null) + throw new ArgumentNullException(nameof(registration)); + DataModelStore.Remove(registration); + } + + public List GetDataModels() + { + return DataModelStore.GetAll().Select(d => d.DataModel).ToList(); + } + + public T GetDataModel() where T : DataModel + { + return (T) DataModelStore.GetAll().FirstOrDefault(d => d.DataModel is T)?.DataModel; + } + + public DataModel GetPluginDataModel(Plugin plugin) + { + return DataModelStore.Get(plugin.PluginInfo.Guid)?.DataModel; + } + + public DataModel GetPluginDataModel(Guid pluginGuid) + { + return DataModelStore.Get(pluginGuid)?.DataModel; + } + + private void PluginServiceOnPluginEnabled(object sender, PluginEventArgs e) + { + if (e.PluginInfo.Instance is Module module) + AddModuleDataModel(module); + else if (e.PluginInfo.Instance is BaseDataModelExpansion dataModelExpansion) + AddDataModelExpansionDataModel(dataModelExpansion); + } + + private void AddModuleDataModel(Module module) + { + if (module.InternalDataModel == null) + return; + + if (module.InternalDataModel.DataModelDescription == null) + throw new ArtemisPluginException(module.PluginInfo, "Module overrides GetDataModelDescription but returned null"); + + module.InternalDataModel.IsExpansion = module.InternalExpandsMainDataModel; + RegisterDataModel(module.InternalDataModel); + } + + private void AddDataModelExpansionDataModel(BaseDataModelExpansion dataModelExpansion) + { + if (dataModelExpansion.InternalDataModel.DataModelDescription == null) + throw new ArtemisPluginException(dataModelExpansion.PluginInfo, "Data model expansion overrides GetDataModelDescription but returned null"); + + dataModelExpansion.InternalDataModel.IsExpansion = true; + RegisterDataModel(dataModelExpansion.InternalDataModel); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/Registration/Interfaces/IConditionOperatorService.cs b/src/Artemis.Core/Services/Registration/Interfaces/IConditionOperatorService.cs new file mode 100644 index 000000000..f59eb587d --- /dev/null +++ b/src/Artemis.Core/Services/Registration/Interfaces/IConditionOperatorService.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using Artemis.Core.Properties; + +namespace Artemis.Core.Services +{ + /// + /// A service that allows you to register and retrieve conditions operators used by display conditions + /// + public interface IConditionOperatorService : IArtemisService + { + /// + /// Registers a new condition operator for use in layer conditions + /// + /// The PluginInfo of the plugin this condition operator belongs to + /// The condition operator to register + ConditionOperatorRegistration RegisterConditionOperator([NotNull] PluginInfo pluginInfo, [NotNull] ConditionOperator conditionOperator); + + /// + /// Removes a condition operator so it is no longer available for use in layer conditions + /// + /// The registration of the condition operator to remove + void RemoveConditionOperator([NotNull] ConditionOperatorRegistration registration); + + /// + /// Returns all the condition operators compatible with the provided type + /// + List GetConditionOperatorsForType(Type type); + + /// + /// Gets a condition operator by its plugin GUID and type name + /// + /// The operator's plugin GUID + /// The type name of the operator + ConditionOperator GetConditionOperator(Guid operatorPluginGuid, string operatorType); + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/Interfaces/IDataBindingService.cs b/src/Artemis.Core/Services/Registration/Interfaces/IDataBindingService.cs similarity index 60% rename from src/Artemis.Core/Services/Interfaces/IDataBindingService.cs rename to src/Artemis.Core/Services/Registration/Interfaces/IDataBindingService.cs index 1ed4bbeb2..b40248b37 100644 --- a/src/Artemis.Core/Services/Interfaces/IDataBindingService.cs +++ b/src/Artemis.Core/Services/Registration/Interfaces/IDataBindingService.cs @@ -1,29 +1,26 @@ using System; using System.Collections.Generic; using Artemis.Core.Properties; -using Newtonsoft.Json; namespace Artemis.Core.Services { + /// + /// A service that allows you to register and retrieve data binding modifiers used by the data bindings system + /// public interface IDataBindingService : IArtemisService { - /// - /// Gets a read-only collection of all registered modifier types - /// - IReadOnlyCollection RegisteredDataBindingModifierTypes { get; } - /// /// Registers a new modifier type for use in data bindings /// /// The PluginInfo of the plugin this modifier type belongs to /// The modifier type to register - void RegisterModifierType([NotNull] PluginInfo pluginInfo, [NotNull] DataBindingModifierType dataBindingModifierType); + DataBindingModifierTypeRegistration RegisterModifierType([NotNull] PluginInfo pluginInfo, [NotNull] DataBindingModifierType dataBindingModifierType); /// /// Removes a modifier type so it is no longer available for use in data bindings /// - /// The modifier type to remove - void RemoveModifierType([NotNull] DataBindingModifierType dataBindingModifierType); + /// The registration of the modifier type to remove + void RemoveModifierType([NotNull] DataBindingModifierTypeRegistration dataBindingModifierType); /// /// Returns all the data binding modifier types compatible with the provided type @@ -37,12 +34,5 @@ namespace Artemis.Core.Services /// The type name of the modifier type /// DataBindingModifierType GetModifierType(Guid modifierTypePluginGuid, string modifierType); - - /// - /// Logs a modifier deserialization failure - /// - /// The modifier that failed to deserialize - /// The JSON exception that occurred - void LogModifierDeserializationFailure(string modifierName, JsonSerializationException exception); } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Registration/Interfaces/IDataModelService.cs b/src/Artemis.Core/Services/Registration/Interfaces/IDataModelService.cs new file mode 100644 index 000000000..c9ab8a279 --- /dev/null +++ b/src/Artemis.Core/Services/Registration/Interfaces/IDataModelService.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using Artemis.Core.DataModelExpansions; + +namespace Artemis.Core.Services +{ + /// + /// A service that allows you to register and retrieve data models + /// + public interface IDataModelService : IArtemisService + { + /// + /// Add a data model to so that it is available to conditions and data bindings + /// + /// + DataModelRegistration RegisterDataModel(DataModel dataModel); + + /// + /// Remove a previously added data model so that it is no longer available + /// + void RemoveDataModel(DataModelRegistration registration); + + /// + /// Returns a list of all registered data models + /// + List GetDataModels(); + + /// + /// If found, returns the registered data model of type + /// + /// The type of the data model to find + T GetDataModel() where T : DataModel; + + /// + /// If found, returns the data model of the provided plugin + /// + /// The plugin to find the data model of + DataModel GetPluginDataModel(Plugin plugin); + + /// + /// If found, returns the data model of the provided plugin GUID + /// + /// The GUID of the plugin to find the data model of + DataModel GetPluginDataModel(Guid pluginGuid); + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/Registration/Interfaces/ILayerBrushService.cs b/src/Artemis.Core/Services/Registration/Interfaces/ILayerBrushService.cs new file mode 100644 index 000000000..8bc6a62e0 --- /dev/null +++ b/src/Artemis.Core/Services/Registration/Interfaces/ILayerBrushService.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using Artemis.Core.LayerBrushes; + +namespace Artemis.Core.Services +{ + /// + /// A service that allows you to register and retrieve layer brushes + /// + public interface ILayerBrushService : IArtemisService + { + /// + /// Add a layer brush descriptor so that it is available to layers + /// + LayerBrushRegistration RegisterLayerBrush(LayerBrushDescriptor descriptor); + + /// + /// Remove a previously added layer brush descriptor so that it is no longer available + /// + void RemoveLayerBrush(LayerBrushRegistration registration); + + /// + /// Returns a list of all registered layer brush descriptors + /// + List GetLayerBrushes(); + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/Registration/Interfaces/ILayerEffectService.cs b/src/Artemis.Core/Services/Registration/Interfaces/ILayerEffectService.cs new file mode 100644 index 000000000..1bb093200 --- /dev/null +++ b/src/Artemis.Core/Services/Registration/Interfaces/ILayerEffectService.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using Artemis.Core.LayerBrushes; +using Artemis.Core.LayerEffects; + +namespace Artemis.Core.Services +{ + /// + /// A service that allows you to register and retrieve layer brushes + /// + public interface ILayerEffectService : IArtemisService + { + /// + /// Add an effect descriptor so that it is available to profile elements + /// + LayerEffectRegistration RegisterLayerEffect(LayerEffectDescriptor descriptor); + + /// + /// Remove a previously added layer effect descriptor so that it is no longer available + /// + void RemoveLayerEffect(LayerEffectRegistration registration); + + /// + /// Returns a list of all registered layer effect descriptors + /// + List GetLayerEffects(); + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/Registration/LayerBrushService.cs b/src/Artemis.Core/Services/Registration/LayerBrushService.cs new file mode 100644 index 000000000..582a988ff --- /dev/null +++ b/src/Artemis.Core/Services/Registration/LayerBrushService.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Artemis.Core.LayerBrushes; +using Ninject; + +namespace Artemis.Core.Services +{ + internal class LayerBrushService : ILayerBrushService + { + private readonly IKernel _kernel; + + public LayerBrushService(IKernel kernel) + { + _kernel = kernel; + } + public LayerBrushRegistration RegisterLayerBrush(LayerBrushDescriptor descriptor) + { + if (descriptor == null) + throw new ArgumentNullException(nameof(descriptor)); + + descriptor.Kernel = _kernel; + return LayerBrushStore.Add(descriptor); + } + + public void RemoveLayerBrush(LayerBrushRegistration registration) + { + if (registration == null) + throw new ArgumentNullException(nameof(registration)); + LayerBrushStore.Remove(registration); + } + + public List GetLayerBrushes() + { + return LayerBrushStore.GetAll().Select(r => r.LayerBrushDescriptor).ToList(); + } + } +} diff --git a/src/Artemis.Core/Services/Registration/LayerEffectService.cs b/src/Artemis.Core/Services/Registration/LayerEffectService.cs new file mode 100644 index 000000000..160c712df --- /dev/null +++ b/src/Artemis.Core/Services/Registration/LayerEffectService.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Artemis.Core.LayerEffects; +using Ninject; + +namespace Artemis.Core.Services +{ + internal class LayerEffectService : ILayerEffectService + { + private readonly IKernel _kernel; + + public LayerEffectService(IKernel kernel) + { + _kernel = kernel; + } + + public LayerEffectRegistration RegisterLayerEffect(LayerEffectDescriptor descriptor) + { + if (descriptor == null) + throw new ArgumentNullException(nameof(descriptor)); + + descriptor.Kernel = _kernel; + return LayerEffectStore.Add(descriptor); + } + + public void RemoveLayerEffect(LayerEffectRegistration registration) + { + if (registration == null) + throw new ArgumentNullException(nameof(registration)); + LayerEffectStore.Remove(registration); + } + + public List GetLayerEffects() + { + return LayerEffectStore.GetAll().Select(r => r.LayerEffectDescriptor).ToList(); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/RenderElementService.cs b/src/Artemis.Core/Services/RenderElementService.cs deleted file mode 100644 index fed3caf7a..000000000 --- a/src/Artemis.Core/Services/RenderElementService.cs +++ /dev/null @@ -1,168 +0,0 @@ -using System; -using System.Linq; -using Artemis.Core.LayerBrushes; -using Artemis.Core.LayerEffects; -using Ninject; -using Serilog; - -namespace Artemis.Core.Services -{ - internal class RenderElementService : IRenderElementService - { - private readonly IDataModelService _dataModelService; - private readonly IDataBindingService _dataBindingService; - private readonly IKernel _kernel; - private readonly ILogger _logger; - private readonly IPluginService _pluginService; - - public RenderElementService(IKernel kernel, ILogger logger, IPluginService pluginService, IDataModelService dataModelService, IDataBindingService dataBindingService) - { - _kernel = kernel; - _logger = logger; - _pluginService = pluginService; - _dataModelService = dataModelService; - _dataBindingService = dataBindingService; - } - - public Layer CreateLayer(Profile profile, ProfileElement parent, string name) - { - var layer = new Layer(profile, parent, name); - parent.AddChild(layer); - - // Layers have two hardcoded property groups, instantiate them - 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); - InstantiateLayerEffects(layer); - InstantiateDisplayConditions(layer); - InstantiateDataBindings(layer); - return layer; - } - - public void RemoveLayerBrush(Layer layer) - { - layer.RemoveLayerBrush(); - layer.OnLayerBrushUpdated(); - } - - public void DeactivateLayerBrush(Layer layer) - { - layer.DeactivateLayerBrush(); - layer.OnLayerBrushUpdated(); - } - - public BaseLayerBrush InstantiateLayerBrush(Layer layer) - { - if (layer.LayerBrush != null) - throw new ArtemisCoreException("Layer already has an instantiated layer brush"); - - var descriptorReference = layer.General.BrushReference?.CurrentValue; - if (descriptorReference == null) - return null; - - // Get a matching descriptor - var layerBrushProviders = _pluginService.GetPluginsOfType(); - var descriptors = layerBrushProviders.SelectMany(l => l.LayerBrushDescriptors).ToList(); - var descriptor = descriptors.FirstOrDefault(d => d.LayerBrushProvider.PluginInfo.Guid == descriptorReference.BrushPluginGuid && - d.LayerBrushType.Name == descriptorReference.BrushType); - - if (descriptor == null) - return null; - - var brush = (BaseLayerBrush) _kernel.Get(descriptor.LayerBrushType); - brush.Layer = layer; - brush.Descriptor = descriptor; - brush.Initialize(); - brush.Update(0); - - layer.LayerBrush = brush; - layer.OnLayerBrushUpdated(); - - return brush; - } - - public BaseLayerEffect AddLayerEffect(RenderProfileElement renderElement, LayerEffectDescriptor layerEffectDescriptor) - { - // Create the effect with dependency injection - var effect = (BaseLayerEffect) _kernel.Get(layerEffectDescriptor.LayerEffectType); - - effect.ProfileElement = renderElement; - effect.EntityId = Guid.NewGuid(); - effect.Enabled = true; - effect.Order = renderElement.LayerEffects.Count + 1; - effect.Descriptor = layerEffectDescriptor; - - effect.Initialize(); - effect.Update(0); - - renderElement.AddLayerEffect(effect); - _logger.Debug("Added layer effect with root path {rootPath}", effect.PropertyRootPath); - return effect; - } - - public void RemoveLayerEffect(BaseLayerEffect layerEffect) - { - layerEffect.ProfileElement.RemoveLayerEffect(layerEffect); - } - - public void InstantiateLayerEffects(RenderProfileElement renderElement) - { - var layerEffectProviders = _pluginService.GetPluginsOfType(); - var descriptors = layerEffectProviders.SelectMany(l => l.LayerEffectDescriptors).ToList(); - var entities = renderElement.RenderElementEntity.LayerEffects.OrderByDescending(e => e.Order).ToList(); - - foreach (var layerEffectEntity in entities) - { - // Skip effects already on the element - if (renderElement.LayerEffects.Any(e => e.EntityId == layerEffectEntity.Id)) - continue; - - // Get a matching descriptor - var descriptor = descriptors.FirstOrDefault(d => d.LayerEffectProvider.PluginInfo.Guid == layerEffectEntity.PluginGuid && - d.LayerEffectType.Name == layerEffectEntity.EffectType); - if (descriptor == null) - continue; - - // Create the effect with dependency injection - var effect = (BaseLayerEffect) _kernel.Get(descriptor.LayerEffectType); - - effect.ProfileElement = renderElement; - effect.EntityId = layerEffectEntity.Id; - effect.Order = layerEffectEntity.Order; - effect.Name = layerEffectEntity.Name; - effect.Enabled = layerEffectEntity.Enabled; - effect.Descriptor = descriptor; - - effect.Initialize(); - effect.Update(0); - - renderElement.AddLayerEffect(effect); - _logger.Debug("Instantiated layer effect with root path {rootPath}", effect.PropertyRootPath); - } - } - - public void InstantiateDisplayConditions(RenderProfileElement renderElement) - { - var displayCondition = renderElement.RenderElementEntity.RootDisplayCondition != null - ? new DisplayConditionGroup(null, renderElement.RenderElementEntity.RootDisplayCondition) - : new DisplayConditionGroup(null); - - try - { - displayCondition.Initialize(_dataModelService); - renderElement.DisplayConditionGroup = displayCondition; - } - catch (Exception e) - { - _logger.Warning(e, $"Failed to init display conditions for {renderElement}"); - } - } - - public void InstantiateDataBindings(RenderProfileElement renderElement) - { - renderElement.InitializeDataBindings(_dataBindingService, _dataModelService); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Services/RgbService.cs b/src/Artemis.Core/Services/RgbService.cs index df10d1a78..c80e36736 100644 --- a/src/Artemis.Core/Services/RgbService.cs +++ b/src/Artemis.Core/Services/RgbService.cs @@ -10,7 +10,7 @@ namespace Artemis.Core.Services /// /// Provides wrapped access the RGB.NET /// - internal class RgbService : IRgbService, IDisposable + internal class RgbService : IRgbService { private readonly List _loadedDevices; private readonly ILogger _logger; diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index 6b50447c4..49c575d8e 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -158,13 +158,13 @@ namespace Artemis.Core.Services profile.RedoStack.Clear(); profile.UndoStack.Push(memento); - profile.ApplyToEntity(); + profile.Save(); if (includeChildren) { foreach (var folder in profile.GetAllFolders()) - folder.ApplyToEntity(); + folder.Save(); foreach (var layer in profile.GetAllLayers()) - layer.ApplyToEntity(); + layer.Save(); } _profileRepository.Save(profile.ProfileEntity); diff --git a/src/Artemis.Core/Stores/ConditionOperatorStore.cs b/src/Artemis.Core/Stores/ConditionOperatorStore.cs new file mode 100644 index 000000000..194188084 --- /dev/null +++ b/src/Artemis.Core/Stores/ConditionOperatorStore.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Artemis.Core +{ + internal class ConditionOperatorStore + { + private static readonly List Registrations = new List(); + + public static ConditionOperatorRegistration Add(ConditionOperator conditionOperator) + { + ConditionOperatorRegistration registration; + lock (Registrations) + { + if (Registrations.Any(r => r.ConditionOperator == conditionOperator)) + throw new ArtemisCoreException($"Condition operator store store already contains operator '{conditionOperator.Description}'"); + + registration = new ConditionOperatorRegistration(conditionOperator, conditionOperator.PluginInfo.Instance) {IsInStore = true}; + Registrations.Add(registration); + } + + OnConditionOperatorAdded(new ConditionOperatorStoreEvent(registration)); + return registration; + } + + public static void Remove(ConditionOperatorRegistration registration) + { + lock (Registrations) + { + if (!Registrations.Contains(registration)) + throw new ArtemisCoreException($"Condition operator store does not contain operator '{registration.ConditionOperator.Description}'"); + + Registrations.Remove(registration); + registration.IsInStore = false; + } + + OnConditionOperatorRemoved(new ConditionOperatorStoreEvent(registration)); + } + + public static ConditionOperatorRegistration Get(Guid pluginGuid, string type) + { + lock (Registrations) + { + return Registrations.FirstOrDefault(r => r.Plugin.PluginInfo.Guid == pluginGuid && r.ConditionOperator.GetType().Name == type); + } + } + + public static List GetForType(Type type) + { + lock (Registrations) + { + if (type == null) + return new List(Registrations); + + var candidates = Registrations.Where(r => r.ConditionOperator.CompatibleTypes.Any(t => t.IsCastableFrom(type))).ToList(); + + // If there are multiple operators with the same description, use the closest match + foreach (var displayConditionOperators in candidates.GroupBy(r => r.ConditionOperator.Description).Where(g => g.Count() > 1).ToList()) + { + var closest = displayConditionOperators.OrderByDescending(r => r.ConditionOperator.CompatibleTypes.Contains(type)).FirstOrDefault(); + foreach (var displayConditionOperator in displayConditionOperators) + { + if (displayConditionOperator != closest) + candidates.Remove(displayConditionOperator); + } + } + + return candidates; + } + } + + #region Events + + public static event EventHandler ConditionOperatorAdded; + public static event EventHandler ConditionOperatorRemoved; + + private static void OnConditionOperatorAdded(ConditionOperatorStoreEvent e) + { + ConditionOperatorAdded?.Invoke(null, e); + } + + private static void OnConditionOperatorRemoved(ConditionOperatorStoreEvent e) + { + ConditionOperatorRemoved?.Invoke(null, e); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Stores/DataBindingModifierTypeStore.cs b/src/Artemis.Core/Stores/DataBindingModifierTypeStore.cs new file mode 100644 index 000000000..b398ed925 --- /dev/null +++ b/src/Artemis.Core/Stores/DataBindingModifierTypeStore.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Artemis.Core +{ + internal class DataBindingModifierTypeStore + { + private static readonly List Registrations = new List(); + + public static DataBindingModifierTypeRegistration Add(DataBindingModifierType modifierType) + { + DataBindingModifierTypeRegistration typeRegistration; + lock (Registrations) + { + if (Registrations.Any(r => r.DataBindingModifierType == modifierType)) + throw new ArtemisCoreException($"Data binding modifier type store already contains modifier '{modifierType.Description}'"); + + typeRegistration = new DataBindingModifierTypeRegistration(modifierType, modifierType.PluginInfo.Instance) { IsInStore = true }; + Registrations.Add(typeRegistration); + } + + OnDataBindingModifierAdded(new DataBindingModifierTypeStoreEvent(typeRegistration)); + return typeRegistration; + } + + public static void Remove(DataBindingModifierTypeRegistration typeRegistration) + { + lock (Registrations) + { + if (!Registrations.Contains(typeRegistration)) + throw new ArtemisCoreException($"Data binding modifier type store does not contain modifier type '{typeRegistration.DataBindingModifierType.Description}'"); + + Registrations.Remove(typeRegistration); + typeRegistration.IsInStore = false; + } + + OnDataBindingModifierRemoved(new DataBindingModifierTypeStoreEvent(typeRegistration)); + } + + public static DataBindingModifierTypeRegistration Get(Guid pluginGuid, string type) + { + lock (Registrations) + { + return Registrations.FirstOrDefault(r => r.Plugin.PluginInfo.Guid == pluginGuid && r.DataBindingModifierType.GetType().Name == type); + } + } + + public static List GetForType(Type type) + { + lock (Registrations) + { + if (type == null) + return new List(Registrations); + + var candidates = Registrations.Where(r => r.DataBindingModifierType.CompatibleTypes.Any(t => t.IsCastableFrom(type))).ToList(); + + // If there are multiple operators with the same description, use the closest match + foreach (var displayDataBindingModifiers in candidates.GroupBy(r => r.DataBindingModifierType.Description).Where(g => g.Count() > 1).ToList()) + { + var closest = displayDataBindingModifiers.OrderByDescending(r => r.DataBindingModifierType.CompatibleTypes.Contains(type)).FirstOrDefault(); + foreach (var displayDataBindingModifier in displayDataBindingModifiers) + { + if (displayDataBindingModifier != closest) + candidates.Remove(displayDataBindingModifier); + } + } + + return candidates; + } + } + + #region Events + + public static event EventHandler DataBindingModifierAdded; + public static event EventHandler DataBindingModifierRemoved; + + private static void OnDataBindingModifierAdded(DataBindingModifierTypeStoreEvent e) + { + DataBindingModifierAdded?.Invoke(null, e); + } + + private static void OnDataBindingModifierRemoved(DataBindingModifierTypeStoreEvent e) + { + DataBindingModifierRemoved?.Invoke(null, e); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Stores/DataModelStore.cs b/src/Artemis.Core/Stores/DataModelStore.cs new file mode 100644 index 000000000..2617e91f7 --- /dev/null +++ b/src/Artemis.Core/Stores/DataModelStore.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Artemis.Core.DataModelExpansions; + +namespace Artemis.Core +{ + internal class DataModelStore + { + private static readonly List Registrations = new List(); + + public static DataModelRegistration Add(DataModel dataModel) + { + DataModelRegistration registration; + lock (Registrations) + { + if (Registrations.Any(r => r.DataModel == dataModel)) + throw new ArtemisCoreException($"Data model store already contains data model '{dataModel.DataModelDescription}'"); + + registration = new DataModelRegistration(dataModel, dataModel.PluginInfo.Instance) {IsInStore = true}; + Registrations.Add(registration); + } + + OnDataModelAdded(new DataModelStoreEvent(registration)); + return registration; + } + + public static void Remove(DataModelRegistration registration) + { + lock (Registrations) + { + if (!Registrations.Contains(registration)) + throw new ArtemisCoreException($"Data model store does not contain data model '{registration.DataModel.DataModelDescription}'"); + + Registrations.Remove(registration); + registration.IsInStore = false; + } + + OnDataModelRemoved(new DataModelStoreEvent(registration)); + } + + public static List GetAll() + { + lock (Registrations) + { + return new List(Registrations); + } + } + + public static DataModelRegistration Get(Guid pluginGuid) + { + lock (Registrations) + { + return Registrations.FirstOrDefault(d => d.Plugin.PluginInfo.Guid == pluginGuid); + } + } + + #region Events + + public static event EventHandler DataModelAdded; + public static event EventHandler DataModelRemoved; + + private static void OnDataModelAdded(DataModelStoreEvent e) + { + DataModelAdded?.Invoke(null, e); + } + + private static void OnDataModelRemoved(DataModelStoreEvent e) + { + DataModelRemoved?.Invoke(null, e); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Stores/LayerBrushStore.cs b/src/Artemis.Core/Stores/LayerBrushStore.cs new file mode 100644 index 000000000..75af208b0 --- /dev/null +++ b/src/Artemis.Core/Stores/LayerBrushStore.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Artemis.Core.LayerBrushes; + +namespace Artemis.Core +{ + internal class LayerBrushStore + { + private static readonly List Registrations = new List(); + + public static LayerBrushRegistration Add(LayerBrushDescriptor descriptor) + { + LayerBrushRegistration registration; + lock (Registrations) + { + if (Registrations.Any(r => r.LayerBrushDescriptor == descriptor)) + throw new ArtemisCoreException($"Store already contains layer brush '{descriptor.DisplayName}'"); + + registration = new LayerBrushRegistration(descriptor, descriptor.LayerBrushProvider.PluginInfo.Instance) {IsInStore = true}; + Registrations.Add(registration); + } + + OnLayerBrushAdded(new LayerBrushStoreEvent(registration)); + return registration; + } + + public static void Remove(LayerBrushRegistration registration) + { + lock (Registrations) + { + if (!Registrations.Contains(registration)) + throw new ArtemisCoreException($"Store does not contain layer brush '{registration.LayerBrushDescriptor.DisplayName}'"); + + Registrations.Remove(registration); + registration.IsInStore = false; + } + + OnLayerBrushRemoved(new LayerBrushStoreEvent(registration)); + } + + public static List GetAll() + { + lock (Registrations) + { + return new List(Registrations); + } + } + + public static LayerBrushRegistration Get(Guid pluginGuid, string typeName) + { + lock (Registrations) + { + return Registrations.FirstOrDefault(d => d.Plugin.PluginInfo.Guid == pluginGuid && d.LayerBrushDescriptor.LayerBrushType.Name == typeName); + } + } + + #region Events + + public static event EventHandler LayerBrushAdded; + public static event EventHandler LayerBrushRemoved; + + private static void OnLayerBrushAdded(LayerBrushStoreEvent e) + { + LayerBrushAdded?.Invoke(null, e); + } + + private static void OnLayerBrushRemoved(LayerBrushStoreEvent e) + { + LayerBrushRemoved?.Invoke(null, e); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Stores/LayerEffectStore.cs b/src/Artemis.Core/Stores/LayerEffectStore.cs new file mode 100644 index 000000000..7e78e22bc --- /dev/null +++ b/src/Artemis.Core/Stores/LayerEffectStore.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Artemis.Core.LayerEffects; + +namespace Artemis.Core +{ + internal class LayerEffectStore + { + private static readonly List Registrations = new List(); + + public static LayerEffectRegistration Add(LayerEffectDescriptor descriptor) + { + LayerEffectRegistration registration; + lock (Registrations) + { + if (Registrations.Any(r => r.LayerEffectDescriptor == descriptor)) + throw new ArtemisCoreException($"Store already contains layer brush '{descriptor.DisplayName}'"); + + registration = new LayerEffectRegistration(descriptor, descriptor.LayerEffectProvider.PluginInfo.Instance) { IsInStore = true }; + Registrations.Add(registration); + } + + OnLayerEffectAdded(new LayerEffectStoreEvent(registration)); + return registration; + } + + public static void Remove(LayerEffectRegistration registration) + { + lock (Registrations) + { + if (!Registrations.Contains(registration)) + throw new ArtemisCoreException($"Store does not contain layer brush '{registration.LayerEffectDescriptor.DisplayName}'"); + + Registrations.Remove(registration); + registration.IsInStore = false; + } + + OnLayerEffectRemoved(new LayerEffectStoreEvent(registration)); + } + + public static List GetAll() + { + lock (Registrations) + { + return new List(Registrations); + } + } + + public static LayerEffectRegistration Get(Guid pluginGuid, string typeName) + { + lock (Registrations) + { + return Registrations.FirstOrDefault(d => d.Plugin.PluginInfo.Guid == pluginGuid && d.LayerEffectDescriptor.LayerEffectType.Name == typeName); + } + } + + #region Events + + public static event EventHandler LayerEffectAdded; + public static event EventHandler LayerEffectRemoved; + + private static void OnLayerEffectAdded(LayerEffectStoreEvent e) + { + LayerEffectAdded?.Invoke(null, e); + } + + private static void OnLayerEffectRemoved(LayerEffectStoreEvent e) + { + LayerEffectRemoved?.Invoke(null, e); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Stores/Registrations/ConditionOperatorRegistration.cs b/src/Artemis.Core/Stores/Registrations/ConditionOperatorRegistration.cs new file mode 100644 index 000000000..babd6da7e --- /dev/null +++ b/src/Artemis.Core/Stores/Registrations/ConditionOperatorRegistration.cs @@ -0,0 +1,40 @@ +using System; + +namespace Artemis.Core +{ + /// + /// Represents a data model registration + /// + public class ConditionOperatorRegistration + { + internal ConditionOperatorRegistration(ConditionOperator conditionOperator, Plugin plugin) + { + ConditionOperator = conditionOperator; + Plugin = plugin; + + Plugin.PluginDisabled += PluginOnPluginDisabled; + } + + /// + /// Gets the condition operator that has been registered + /// + public ConditionOperator ConditionOperator { get; } + + /// + /// Gets the plugin the condition operator is associated with + /// + public Plugin Plugin { get; } + + /// + /// Gets a boolean indicating whether the registration is in the internal Core store + /// + public bool IsInStore { get; internal set; } + + private void PluginOnPluginDisabled(object sender, EventArgs e) + { + Plugin.PluginDisabled -= PluginOnPluginDisabled; + if (IsInStore) + ConditionOperatorStore.Remove(this); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Stores/Registrations/DataBindingModifierTypeRegistration.cs b/src/Artemis.Core/Stores/Registrations/DataBindingModifierTypeRegistration.cs new file mode 100644 index 000000000..d00e19444 --- /dev/null +++ b/src/Artemis.Core/Stores/Registrations/DataBindingModifierTypeRegistration.cs @@ -0,0 +1,40 @@ +using System; + +namespace Artemis.Core +{ + /// + /// Represents a data model registration + /// + public class DataBindingModifierTypeRegistration + { + internal DataBindingModifierTypeRegistration(DataBindingModifierType dataBindingModifierType, Plugin plugin) + { + DataBindingModifierType = dataBindingModifierType; + Plugin = plugin; + + Plugin.PluginDisabled += PluginOnPluginDisabled; + } + + /// + /// Gets the data binding modifier that has been registered + /// + public DataBindingModifierType DataBindingModifierType { get; } + + /// + /// Gets the plugin the data binding modifier is associated with + /// + public Plugin Plugin { get; } + + /// + /// Gets a boolean indicating whether the registration is in the internal Core store + /// + public bool IsInStore { get; internal set; } + + private void PluginOnPluginDisabled(object sender, EventArgs e) + { + Plugin.PluginDisabled -= PluginOnPluginDisabled; + if (IsInStore) + DataBindingModifierTypeStore.Remove(this); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Stores/Registrations/DataModelRegistration.cs b/src/Artemis.Core/Stores/Registrations/DataModelRegistration.cs new file mode 100644 index 000000000..336a64c96 --- /dev/null +++ b/src/Artemis.Core/Stores/Registrations/DataModelRegistration.cs @@ -0,0 +1,41 @@ +using System; +using Artemis.Core.DataModelExpansions; + +namespace Artemis.Core +{ + /// + /// Represents a data model registration + /// + public class DataModelRegistration + { + internal DataModelRegistration(DataModel dataModel, Plugin plugin) + { + DataModel = dataModel; + Plugin = plugin; + + Plugin.PluginDisabled += PluginOnPluginDisabled; + } + + /// + /// Gets the data model that has been registered + /// + public DataModel DataModel { get; } + + /// + /// Gets the plugin the data model is associated with + /// + public Plugin Plugin { get; } + + /// + /// Gets a boolean indicating whether the registration is in the internal Core store + /// + public bool IsInStore { get; internal set; } + + private void PluginOnPluginDisabled(object sender, EventArgs e) + { + Plugin.PluginDisabled -= PluginOnPluginDisabled; + if (IsInStore) + DataModelStore.Remove(this); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Stores/Registrations/LayerBrushRegistration.cs b/src/Artemis.Core/Stores/Registrations/LayerBrushRegistration.cs new file mode 100644 index 000000000..eeda91812 --- /dev/null +++ b/src/Artemis.Core/Stores/Registrations/LayerBrushRegistration.cs @@ -0,0 +1,41 @@ +using System; +using Artemis.Core.LayerBrushes; + +namespace Artemis.Core +{ + /// + /// Represents a layer brush registration + /// + public class LayerBrushRegistration + { + internal LayerBrushRegistration(LayerBrushDescriptor descriptor, Plugin plugin) + { + LayerBrushDescriptor = descriptor; + Plugin = plugin; + + Plugin.PluginDisabled += PluginOnPluginDisabled; + } + + /// + /// Gets the layer brush descriptor that has been registered + /// + public LayerBrushDescriptor LayerBrushDescriptor { get; } + + /// + /// Gets the plugin the layer brush is associated with + /// + public Plugin Plugin { get; } + + /// + /// Gets a boolean indicating whether the registration is in the internal Core store + /// + public bool IsInStore { get; internal set; } + + private void PluginOnPluginDisabled(object sender, EventArgs e) + { + Plugin.PluginDisabled -= PluginOnPluginDisabled; + if (IsInStore) + LayerBrushStore.Remove(this); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Stores/Registrations/LayerEffectRegistration.cs b/src/Artemis.Core/Stores/Registrations/LayerEffectRegistration.cs new file mode 100644 index 000000000..992acf616 --- /dev/null +++ b/src/Artemis.Core/Stores/Registrations/LayerEffectRegistration.cs @@ -0,0 +1,41 @@ +using System; +using Artemis.Core.LayerEffects; + +namespace Artemis.Core +{ + /// + /// Represents a layer effect registration + /// + public class LayerEffectRegistration + { + internal LayerEffectRegistration(LayerEffectDescriptor descriptor, Plugin plugin) + { + LayerEffectDescriptor = descriptor; + Plugin = plugin; + + Plugin.PluginDisabled += PluginOnPluginDisabled; + } + + /// + /// Gets the layer effect descriptor that has been registered + /// + public LayerEffectDescriptor LayerEffectDescriptor { get; } + + /// + /// Gets the plugin the layer effect is associated with + /// + public Plugin Plugin { get; } + + /// + /// Gets a boolean indicating whether the registration is in the internal Core store + /// + public bool IsInStore { get; internal set; } + + private void PluginOnPluginDisabled(object sender, EventArgs e) + { + Plugin.PluginDisabled -= PluginOnPluginDisabled; + if (IsInStore) + LayerEffectStore.Remove(this); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Utilities/DeserializationLogger.cs b/src/Artemis.Core/Utilities/DeserializationLogger.cs new file mode 100644 index 000000000..1be7ad7fb --- /dev/null +++ b/src/Artemis.Core/Utilities/DeserializationLogger.cs @@ -0,0 +1,44 @@ +using Newtonsoft.Json; +using Ninject; +using Serilog; + +namespace Artemis.Core +{ + internal static class DeserializationLogger + { + private static ILogger _logger; + + public static void Initialize(IKernel kernel) + { + _logger = kernel.Get(); + } + + public static void LogPredicateDeserializationFailure(DisplayConditionPredicate displayConditionPredicate, JsonException exception) + { + _logger.Warning( + exception, + "Failed to deserialize display condition predicate {left} {operator} {right}", + displayConditionPredicate.Entity.LeftPropertyPath, + displayConditionPredicate.Entity.OperatorType, + displayConditionPredicate.Entity.RightPropertyPath + ); + } + + public static void LogListPredicateDeserializationFailure(DisplayConditionListPredicate displayConditionPredicate, JsonException exception) + { + _logger.Warning( + exception, + "Failed to deserialize display condition list predicate {list} => {left} {operator} {right}", + displayConditionPredicate.Entity.ListPropertyPath, + displayConditionPredicate.Entity.LeftPropertyPath, + displayConditionPredicate.Entity.OperatorType, + displayConditionPredicate.Entity.RightPropertyPath + ); + } + + public static void LogModifierDeserializationFailure(string modifierName, JsonSerializationException exception) + { + _logger.Warning(exception, "Failed to deserialize static parameter for modifier {modifierName}", modifierName); + } + } +} diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListPredicateViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListPredicateViewModel.cs index e02fadd81..08e7165ec 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListPredicateViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListPredicateViewModel.cs @@ -26,13 +26,13 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions private readonly Timer _updateTimer; private bool _isInitialized; private DataModelVisualizationViewModel _leftSideDataModel; - private BindableCollection _operators; + private BindableCollection _operators; private DataModelVisualizationViewModel _rightSideDataModel; private DataModelInputViewModel _rightSideInputViewModel; private int _rightSideTransitionIndex; private object _rightStaticValue; private DataModelVisualizationViewModel _selectedLeftSideProperty; - private DisplayConditionOperator _selectedOperator; + private ConditionOperator _selectedOperator; private DataModelVisualizationViewModel _selectedRightSideProperty; private List _supportedInputTypes; @@ -56,7 +56,7 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions SelectLeftPropertyCommand = new DelegateCommand(ExecuteSelectLeftProperty); SelectRightPropertyCommand = new DelegateCommand(ExecuteSelectRightProperty); SelectOperatorCommand = new DelegateCommand(ExecuteSelectOperatorCommand); - Operators = new BindableCollection(); + Operators = new BindableCollection(); ShowDataModelValues = settingsService.GetSetting("ProfileEditor.ShowDataModelValues"); @@ -125,13 +125,13 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions set => SetAndNotify(ref _rightSideInputViewModel, value); } - public BindableCollection Operators + public BindableCollection Operators { get => _operators; set => SetAndNotify(ref _operators, value); } - public DisplayConditionOperator SelectedOperator + public ConditionOperator SelectedOperator { get => _selectedOperator; set => SetAndNotify(ref _selectedOperator, value); @@ -206,7 +206,7 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions // Get the supported operators Operators.Clear(); - Operators.AddRange(_dataModelService.GetCompatibleConditionOperators(leftSideType)); + Operators.AddRange(_dataModelService.GetConditionOperatorsForType(leftSideType)); if (DisplayConditionListPredicate.Operator == null) DisplayConditionListPredicate.UpdateOperator(Operators.FirstOrDefault(o => o.SupportsType(leftSideType))); SelectedOperator = DisplayConditionListPredicate.Operator; @@ -347,7 +347,7 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions private void ExecuteSelectOperatorCommand(object context) { - if (!(context is DisplayConditionOperator displayConditionOperator)) + if (!(context is ConditionOperator displayConditionOperator)) return; SelectedOperator = displayConditionOperator; diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs index 2b38b9c51..a7750ee91 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs @@ -25,13 +25,13 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions private readonly Timer _updateTimer; private bool _isInitialized; private DataModelPropertiesViewModel _leftSideDataModel; - private BindableCollection _operators; + private BindableCollection _operators; private DataModelPropertiesViewModel _rightSideDataModel; private DataModelInputViewModel _rightSideInputViewModel; private int _rightSideTransitionIndex; private object _rightStaticValue; private DataModelVisualizationViewModel _selectedLeftSideProperty; - private DisplayConditionOperator _selectedOperator; + private ConditionOperator _selectedOperator; private DataModelVisualizationViewModel _selectedRightSideProperty; private List _supportedInputTypes; @@ -55,7 +55,7 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions SelectLeftPropertyCommand = new DelegateCommand(ExecuteSelectLeftProperty); SelectRightPropertyCommand = new DelegateCommand(ExecuteSelectRightProperty); SelectOperatorCommand = new DelegateCommand(ExecuteSelectOperatorCommand); - Operators = new BindableCollection(); + Operators = new BindableCollection(); ShowDataModelValues = settingsService.GetSetting("ProfileEditor.ShowDataModelValues"); @@ -124,13 +124,13 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions set => SetAndNotify(ref _rightSideInputViewModel, value); } - public BindableCollection Operators + public BindableCollection Operators { get => _operators; set => SetAndNotify(ref _operators, value); } - public DisplayConditionOperator SelectedOperator + public ConditionOperator SelectedOperator { get => _selectedOperator; set => SetAndNotify(ref _selectedOperator, value); @@ -208,7 +208,7 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions // Get the supported operators Operators.Clear(); - Operators.AddRange(_dataModelService.GetCompatibleConditionOperators(leftSideType)); + Operators.AddRange(_dataModelService.GetConditionOperatorsForType(leftSideType)); if (DisplayConditionPredicate.Operator == null) DisplayConditionPredicate.UpdateOperator(Operators.FirstOrDefault(o => o.SupportsType(leftSideType))); SelectedOperator = DisplayConditionPredicate.Operator; @@ -329,7 +329,7 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions private void ExecuteSelectOperatorCommand(object context) { - if (!(context is DisplayConditionOperator displayConditionOperator)) + if (!(context is ConditionOperator displayConditionOperator)) return; SelectedOperator = displayConditionOperator; From 1de6fefc2a121f7b5b2c06543cb599d4a666941c Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Wed, 9 Sep 2020 23:01:36 +0200 Subject: [PATCH 04/12] Display conditions - Implemented IDisposable Display conditions - Listen to registration add/remove WIP Data bindings - Implemented IDisposable Data bindings - Listen to registration add/remove --- .../Abstract/DisplayConditionPart.cs | 23 ++- .../Conditions/DisplayConditionGroup.cs | 21 +++ .../Conditions/DisplayConditionList.cs | 68 +++++++- .../Conditions/DisplayConditionPredicate.cs | 40 +++++ .../Profile/DataBindings/DataBinding.cs | 98 ++++++++--- .../DataBindings/DataBindingModifier.cs | 154 +++++++++++++----- .../Profile/DataBindings/IDataBinding.cs | 16 +- .../DataBindings/IDataBindingModifier.cs | 6 +- .../Abstract/DataBindingModifierType.cs | 34 ---- .../Profile/LayerProperties/ILayerProperty.cs | 5 +- .../Profile/LayerProperties/LayerProperty.cs | 62 ++++++- .../Models/Profile/LayerPropertyGroup.cs | 18 +- src/Artemis.Core/Models/Profile/Profile.cs | 4 +- .../Models/Profile/RenderProfileElement.cs | 17 ++ .../Services/Storage/ProfileService.cs | 101 +----------- .../PropertyGroups/ColorBrushProperties.cs | 15 +- .../RadialGradientProperties.cs | 6 +- 17 files changed, 461 insertions(+), 227 deletions(-) diff --git a/src/Artemis.Core/Models/Profile/Conditions/Abstract/DisplayConditionPart.cs b/src/Artemis.Core/Models/Profile/Conditions/Abstract/DisplayConditionPart.cs index 2669afc17..72ca6a7c4 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/Abstract/DisplayConditionPart.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Abstract/DisplayConditionPart.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Artemis.Core.Services; using Artemis.Storage.Entities.Profile.Abstract; @@ -7,7 +8,7 @@ namespace Artemis.Core /// /// An abstract class for display condition parts /// - public abstract class DisplayConditionPart + public abstract class DisplayConditionPart : IDisposable { private readonly List _children = new List(); @@ -62,5 +63,23 @@ namespace Artemis.Core internal abstract void ApplyToEntity(); internal abstract DisplayConditionPartEntity GetEntity(); + + #region IDisposable + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + #endregion + } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionGroup.cs b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionGroup.cs index e68dd9e57..cc4cc7e49 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionGroup.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionGroup.cs @@ -11,6 +11,8 @@ namespace Artemis.Core /// public class DisplayConditionGroup : DisplayConditionPart { + private bool _disposed; + /// /// Creates a new instance of the class /// @@ -55,6 +57,9 @@ namespace Artemis.Core /// public override bool Evaluate() { + if (_disposed) + throw new ObjectDisposedException("DisplayConditionGroup"); + // Empty groups are always true if (Children.Count == 0) return true; @@ -80,6 +85,9 @@ namespace Artemis.Core /// public override bool EvaluateObject(object target) { + if (_disposed) + throw new ObjectDisposedException("DisplayConditionGroup"); + // Empty groups are always true if (Children.Count == 0) return true; @@ -111,6 +119,19 @@ namespace Artemis.Core { return Entity; } + + #region IDisposable + + protected override void Dispose(bool disposing) + { + _disposed = true; + foreach (var child in Children) + child.Dispose(); + + base.Dispose(disposing); + } + + #endregion } public enum BooleanOperator diff --git a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionList.cs b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionList.cs index bd9c54b76..155ae3489 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionList.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionList.cs @@ -10,6 +10,8 @@ namespace Artemis.Core { public class DisplayConditionList : DisplayConditionPart { + private bool _disposed; + public DisplayConditionList(DisplayConditionPart parent) { Parent = parent; @@ -37,6 +39,9 @@ namespace Artemis.Core public override bool Evaluate() { + if (_disposed) + throw new ObjectDisposedException("DisplayConditionList"); + if (CompiledListAccessor == null) return false; @@ -45,6 +50,9 @@ namespace Artemis.Core public override bool EvaluateObject(object target) { + if (_disposed) + throw new ObjectDisposedException("DisplayConditionList"); + if (!Children.Any()) return false; if (!(target is IList list)) @@ -63,6 +71,9 @@ namespace Artemis.Core public void UpdateList(DataModel dataModel, string path) { + if (_disposed) + throw new ObjectDisposedException("DisplayConditionList"); + if (dataModel != null && path == null) throw new ArtemisCoreException("If a data model is provided, a path is also required"); if (dataModel == null && path != null) @@ -93,6 +104,9 @@ namespace Artemis.Core public void CreateExpression() { + if (_disposed) + throw new ObjectDisposedException("DisplayConditionList"); + var parameter = Expression.Parameter(typeof(object), "listDataModel"); var accessor = ListPropertyPath.Split('.').Aggregate( Expression.Convert(parameter, ListDataModel.GetType()), @@ -101,12 +115,15 @@ namespace Artemis.Core var lambda = Expression.Lambda>(accessor, parameter); CompiledListAccessor = lambda.Compile(); } - + internal override void ApplyToEntity() { // Target list - Entity.ListDataModelGuid = ListDataModel?.PluginInfo?.Guid; - Entity.ListPropertyPath = ListPropertyPath; + if (ListDataModel != null) + { + Entity.ListDataModelGuid = ListDataModel.PluginInfo.Guid; + Entity.ListPropertyPath = ListPropertyPath; + } // Operator Entity.ListOperator = (int) ListOperator; @@ -125,6 +142,8 @@ namespace Artemis.Core internal void Initialize() { + DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded; + DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved; if (Entity.ListDataModelGuid == null) return; @@ -147,6 +166,49 @@ namespace Artemis.Core AddChild(new DisplayConditionGroup(this)); } } + + + #region Event handlers + + private void DataModelStoreOnDataModelAdded(object sender, DataModelStoreEvent e) + { + var dataModel = e.Registration.DataModel; + if (dataModel.PluginInfo.Guid == Entity.ListDataModelGuid && dataModel.ContainsPath(Entity.ListPropertyPath)) + { + ListDataModel = dataModel; + ListPropertyPath = Entity.ListPropertyPath; + CreateExpression(); + } + } + + private void DataModelStoreOnDataModelRemoved(object sender, DataModelStoreEvent e) + { + if (ListDataModel != e.Registration.DataModel) + return; + + ListDataModel = null; + CompiledListAccessor = null; + } + + #endregion + + #region IDisposable + + /// + protected override void Dispose(bool disposing) + { + _disposed = true; + + DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded; + DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved; + + foreach (var child in Children) + child.Dispose(); + + base.Dispose(disposing); + } + + #endregion } public enum ListOperator diff --git a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs index 70a7a890a..53d70de8a 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs @@ -248,6 +248,9 @@ namespace Artemis.Core internal void Initialize() { + DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded; + DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved; + // Left side if (Entity.LeftDataModelGuid != null) { @@ -424,5 +427,42 @@ namespace Artemis.Core Expression.Property ); } + + #region Event handlers + + private void DataModelStoreOnDataModelAdded(object sender, DataModelStoreEvent e) + { + var dataModel = e.Registration.DataModel; + if (dataModel.PluginInfo.Guid == Entity.LeftDataModelGuid && dataModel.ContainsPath(Entity.LeftPropertyPath)) + UpdateLeftSide(dataModel, Entity.LeftPropertyPath); + if (dataModel.PluginInfo.Guid == Entity.RightDataModelGuid && dataModel.ContainsPath(Entity.RightPropertyPath)) + UpdateRightSide(dataModel, Entity.RightPropertyPath); + } + + private void DataModelStoreOnDataModelRemoved(object sender, DataModelStoreEvent e) + { + if (LeftDataModel == e.Registration.DataModel) + { + CompiledDynamicPredicate = null; + LeftDataModel = null; + } + + if (RightDataModel == e.Registration.DataModel) + { + CompiledDynamicPredicate = null; + RightDataModel = null; + } + } + + #endregion + + /// + protected override void Dispose(bool disposing) + { + DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded; + DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved; + + base.Dispose(disposing); + } } } \ 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 d3f8bd9f3..7e5240820 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; using Artemis.Core.DataModelExpansions; -using Artemis.Core.Services; using Artemis.Storage.Entities.Profile.DataBindings; namespace Artemis.Core @@ -17,6 +16,7 @@ namespace Artemis.Core private TProperty _currentValue; private TimeSpan _easingProgress; private TProperty _previousValue; + private bool _disposed; internal DataBinding(DataBindingRegistration dataBindingRegistration) { @@ -25,6 +25,7 @@ namespace Artemis.Core ApplyRegistration(dataBindingRegistration); Save(); + Initialize(); } internal DataBinding(LayerProperty layerProperty, DataBindingEntity entity) @@ -32,6 +33,8 @@ namespace Artemis.Core LayerProperty = layerProperty; Entity = entity; + // Load will add children so be initialized before that + Initialize(); Load(); } @@ -95,6 +98,9 @@ namespace Artemis.Core /// The time in seconds that passed since the last update public void Update(double deltaTime) { + if (_disposed) + throw new ObjectDisposedException("DataBinding"); + // Data bindings cannot go back in time like brushes deltaTime = Math.Max(0, deltaTime); @@ -103,11 +109,27 @@ namespace Artemis.Core _easingProgress = EasingTime; } + /// + public void Apply() + { + if (_disposed) + throw new ObjectDisposedException("DataBinding"); + + if (Converter == null) + return; + + var value = GetValue(Converter.GetValue()); + Converter.ApplyValue(GetValue(value)); + } + /// /// Adds a modifier to the data binding's collection /// public void AddModifier(DataBindingModifier modifier) { + if (_disposed) + throw new ObjectDisposedException("DataBinding"); + if (!_modifiers.Contains(modifier)) { modifier.DataBinding = this; @@ -122,6 +144,9 @@ namespace Artemis.Core /// public void RemoveModifier(DataBindingModifier modifier) { + if (_disposed) + throw new ObjectDisposedException("DataBinding"); + if (_modifiers.Contains(modifier)) { modifier.DataBinding = null; @@ -138,6 +163,9 @@ namespace Artemis.Core /// The path pointing to the source inside the data model public void UpdateSource(DataModel dataModel, string path) { + if (_disposed) + throw new ObjectDisposedException("DataBinding"); + if (dataModel != null && path == null) throw new ArtemisCoreException("If a data model is provided, a path is also required"); if (dataModel == null && path != null) @@ -161,6 +189,9 @@ namespace Artemis.Core /// public TProperty GetValue(TProperty baseValue) { + if (_disposed) + throw new ObjectDisposedException("DataBinding"); + if (CompiledTargetAccessor == null || Converter == null) return baseValue; @@ -198,30 +229,18 @@ namespace Artemis.Core return SourceDataModel?.GetTypeAtPath(SourcePropertyPath); } - /// - public void Apply() + private void Initialize() { - if (Converter == null) - return; + DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded; + DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved; - var value = GetValue(Converter.GetValue()); - Converter.ApplyValue(GetValue(value)); - } - - /// - public void Initialize(IDataModelService dataModelService, IDataBindingService dataBindingService) - { // Source if (Entity.SourceDataModelGuid != null && SourceDataModel == null) { - var dataModel = dataModelService.GetPluginDataModelByGuid(Entity.SourceDataModelGuid.Value); + var dataModel = DataModelStore.Get(Entity.SourceDataModelGuid.Value)?.DataModel; if (dataModel != null && dataModel.ContainsPath(Entity.SourcePropertyPath)) UpdateSource(dataModel, Entity.SourcePropertyPath); } - - // Modifiers - foreach (var dataBindingModifier in Modifiers) - dataBindingModifier.Initialize(dataModelService, dataBindingService); } private void ResetEasing(TProperty value) @@ -283,6 +302,8 @@ namespace Artemis.Core /// public void Load() { + if (_disposed) + throw new ObjectDisposedException("DataBinding"); // General var registration = LayerProperty.GetDataBindingRegistration(Entity.TargetProperty); ApplyRegistration(registration); @@ -301,6 +322,9 @@ namespace Artemis.Core /// public void Save() { + if (_disposed) + throw new ObjectDisposedException("DataBinding"); + if (!LayerProperty.Entity.DataBindingEntities.Contains(Entity)) LayerProperty.Entity.DataBindingEntities.Add(Entity); @@ -311,9 +335,12 @@ namespace Artemis.Core Entity.EasingFunction = (int) EasingFunction; // Data model - Entity.SourceDataModelGuid = SourceDataModel?.PluginInfo?.Guid; - Entity.SourcePropertyPath = SourcePropertyPath; - + if (SourceDataModel != null) + { + Entity.SourceDataModelGuid = SourceDataModel.PluginInfo.Guid; + Entity.SourcePropertyPath = SourcePropertyPath; + } + // Modifiers Entity.Modifiers.Clear(); foreach (var dataBindingModifier in Modifiers) @@ -322,6 +349,25 @@ namespace Artemis.Core #endregion + #region Event handlers + + private void DataModelStoreOnDataModelAdded(object sender, DataModelStoreEvent e) + { + var dataModel = e.Registration.DataModel; + if (dataModel.PluginInfo.Guid == Entity.SourceDataModelGuid && dataModel.ContainsPath(Entity.SourcePropertyPath)) + UpdateSource(dataModel, Entity.SourcePropertyPath); + } + + private void DataModelStoreOnDataModelRemoved(object sender, DataModelStoreEvent e) + { + if (SourceDataModel != e.Registration.DataModel) + return; + SourceDataModel = null; + CompiledTargetAccessor = null; + } + + #endregion + #region Events /// @@ -335,6 +381,18 @@ namespace Artemis.Core } #endregion + + /// + public void Dispose() + { + _disposed = true; + + DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded; + DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved; + + foreach (var dataBindingModifier in Modifiers) + dataBindingModifier.Dispose(); + } } /// diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingModifier.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingModifier.cs index 18571fcde..bd25a62cf 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingModifier.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingModifier.cs @@ -2,7 +2,6 @@ using System.Linq; using System.Linq.Expressions; using Artemis.Core.DataModelExpansions; -using Artemis.Core.Services; using Artemis.Storage.Entities.Profile.DataBindings; using Newtonsoft.Json; @@ -12,6 +11,7 @@ namespace Artemis.Core public class DataBindingModifier : IDataBindingModifier { private DataBinding _dataBinding; + private bool _disposed; /// /// Creates a new instance of the class @@ -22,6 +22,7 @@ namespace Artemis.Core ParameterType = parameterType; Entity = new DataBindingModifierEntity(); Save(); + Initialize(); } internal DataBindingModifier(DataBinding dataBinding, DataBindingModifierEntity entity) @@ -29,6 +30,7 @@ namespace Artemis.Core DataBinding = dataBinding; Entity = entity; Load(); + Initialize(); } /// @@ -82,6 +84,52 @@ namespace Artemis.Core internal DataBindingModifierEntity Entity { get; set; } + + /// + public void Save() + { + if (_disposed) + throw new ObjectDisposedException("DataBindingModifier"); + + if (!DataBinding.Entity.Modifiers.Contains(Entity)) + DataBinding.Entity.Modifiers.Add(Entity); + + // Modifier + if (ModifierType != null) + { + Entity.ModifierType = ModifierType.GetType().Name; + Entity.ModifierTypePluginGuid = ModifierType.PluginInfo.Guid; + } + + // General + Entity.Order = Order; + Entity.ParameterType = (int) ParameterType; + + // Parameter + if (ParameterDataModel != null) + { + Entity.ParameterDataModelGuid = ParameterDataModel.PluginInfo.Guid; + Entity.ParameterPropertyPath = ParameterPropertyPath; + } + + Entity.ParameterStaticValue = JsonConvert.SerializeObject(ParameterStaticValue); + } + + /// + public void Load() + { + if (_disposed) + throw new ObjectDisposedException("DataBindingModifier"); + + // Modifier type is done during Initialize + + // General + Order = Entity.Order; + ParameterType = (ProfileRightSideType) Entity.ParameterType; + + // Parameter is done during initialize + } + /// /// Applies the modifier to the provided value /// @@ -89,6 +137,9 @@ namespace Artemis.Core /// The modified value public object Apply(object currentValue) { + if (_disposed) + throw new ObjectDisposedException("DataBindingModifier"); + if (ModifierType == null) return currentValue; @@ -113,6 +164,9 @@ namespace Artemis.Core /// public void UpdateModifierType(DataBindingModifierType modifierType) { + if (_disposed) + throw new ObjectDisposedException("DataBindingModifier"); + // Calling CreateExpression will clear compiled expressions if (modifierType == null) { @@ -139,6 +193,9 @@ namespace Artemis.Core /// The path pointing to the parameter inside the data model public void UpdateParameter(DataModel dataModel, string path) { + if (_disposed) + throw new ObjectDisposedException("DataBindingModifier"); + if (dataModel != null && path == null) throw new ArtemisCoreException("If a data model is provided, a path is also required"); if (dataModel == null && path != null) @@ -163,6 +220,9 @@ namespace Artemis.Core /// The static value to use as a parameter public void UpdateParameter(object staticValue) { + if (_disposed) + throw new ObjectDisposedException("DataBindingModifier"); + ParameterType = ProfileRightSideType.Static; ParameterDataModel = null; ParameterPropertyPath = null; @@ -183,12 +243,17 @@ namespace Artemis.Core CreateExpression(); } - internal void Initialize(IDataModelService dataModelService, IDataBindingService dataBindingService) + private void Initialize() { + DataBindingModifierTypeStore.DataBindingModifierAdded += DataBindingModifierTypeStoreOnDataBindingModifierAdded; + DataBindingModifierTypeStore.DataBindingModifierRemoved += DataBindingModifierTypeStoreOnDataBindingModifierRemoved; + DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded; + DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved; + // Modifier type if (Entity.ModifierTypePluginGuid != null && ModifierType == null) { - var modifierType = dataBindingService.GetModifierType(Entity.ModifierTypePluginGuid.Value, Entity.ModifierType); + var modifierType = DataBindingModifierTypeStore.Get(Entity.ModifierTypePluginGuid.Value, Entity.ModifierType)?.DataBindingModifierType; if (modifierType != null) UpdateModifierType(modifierType); } @@ -196,7 +261,7 @@ namespace Artemis.Core // Dynamic parameter if (ParameterType == ProfileRightSideType.Dynamic && Entity.ParameterDataModelGuid != null && ParameterDataModel == null) { - var dataModel = dataModelService.GetPluginDataModelByGuid(Entity.ParameterDataModelGuid.Value); + var dataModel = DataModelStore.Get(Entity.ParameterDataModelGuid.Value)?.DataModel; if (dataModel != null && dataModel.ContainsPath(Entity.ParameterPropertyPath)) UpdateParameter(dataModel, Entity.ParameterPropertyPath); } @@ -214,7 +279,7 @@ namespace Artemis.Core // If deserialization fails, use the type's default catch (JsonSerializationException e) { - dataBindingService.LogModifierDeserializationFailure(GetType().Name, e); + DeserializationLogger.LogModifierDeserializationFailure(GetType().Name, e); staticValue = Activator.CreateInstance(targetType); } @@ -222,39 +287,6 @@ namespace Artemis.Core } } - /// - 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; - - // General - Entity.Order = Order; - Entity.ParameterType = (int) ParameterType; - - // Parameter - Entity.ParameterDataModelGuid = ParameterDataModel?.PluginInfo.Guid; - Entity.ParameterPropertyPath = ParameterPropertyPath; - Entity.ParameterStaticValue = JsonConvert.SerializeObject(ParameterStaticValue); - } - - /// - public void Load() - { - // Modifier type is done during Initialize - - // General - Order = Entity.Order; - ParameterType = (ProfileRightSideType) Entity.ParameterType; - - // Parameter is done during initialize - } - - private void CreateExpression() { CompiledParameterAccessor = null; @@ -286,5 +318,51 @@ namespace Artemis.Core Expression.Property ); } + + #region Event handlers + + private void DataBindingModifierTypeStoreOnDataBindingModifierAdded(object sender, DataBindingModifierTypeStoreEvent e) + { + if (ModifierType != null) + return; + + var modifierType = e.TypeRegistration.DataBindingModifierType; + if (modifierType.PluginInfo.Guid == Entity.ModifierTypePluginGuid && modifierType.GetType().Name == Entity.ModifierType) + UpdateModifierType(modifierType); + } + + private void DataBindingModifierTypeStoreOnDataBindingModifierRemoved(object sender, DataBindingModifierTypeStoreEvent e) + { + if (e.TypeRegistration.DataBindingModifierType == ModifierType) + UpdateModifierType(null); + } + + private void DataModelStoreOnDataModelAdded(object sender, DataModelStoreEvent e) + { + var dataModel = e.Registration.DataModel; + if (dataModel.PluginInfo.Guid == Entity.ParameterDataModelGuid && dataModel.ContainsPath(Entity.ParameterPropertyPath)) + UpdateParameter(dataModel, Entity.ParameterPropertyPath); + } + + private void DataModelStoreOnDataModelRemoved(object sender, DataModelStoreEvent e) + { + if (e.Registration.DataModel != ParameterDataModel) + return; + ParameterDataModel = null; + CompiledParameterAccessor = null; + } + + #endregion + + /// + public void Dispose() + { + _disposed = true; + + DataBindingModifierTypeStore.DataBindingModifierAdded -= DataBindingModifierTypeStoreOnDataBindingModifierAdded; + DataBindingModifierTypeStore.DataBindingModifierRemoved -= DataBindingModifierTypeStoreOnDataBindingModifierRemoved; + DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded; + DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved; + } } } \ 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 index 6776a9e29..a609b1b3a 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs @@ -1,20 +1,14 @@ -using Artemis.Core.DataModelExpansions; -using Artemis.Core.Services; +using System; +using Artemis.Core.DataModelExpansions; namespace Artemis.Core { /// - /// Represents a data binding that binds a certain to a value inside a + /// Represents a data binding that binds a certain to a value inside a + /// /// - public interface IDataBinding : IStorageModel, IUpdateModel + public interface IDataBinding : IStorageModel, IUpdateModel, IDisposable { - /// - /// (Re)initializes the data binding - /// - /// - /// - void Initialize(IDataModelService dataModelService, IDataBindingService dataBindingService); - /// /// Applies the data binding to the layer property /// diff --git a/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingModifier.cs b/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingModifier.cs index 1192894eb..6f920c0a1 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingModifier.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/IDataBindingModifier.cs @@ -1,9 +1,11 @@ -namespace Artemis.Core +using System; + +namespace Artemis.Core { /// /// Modifies a data model value in a way defined by the modifier type /// - public interface IDataBindingModifier : IStorageModel + public interface IDataBindingModifier : IStorageModel, IDisposable { } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/Modifiers/Abstract/DataBindingModifierType.cs b/src/Artemis.Core/Models/Profile/DataBindings/Modifiers/Abstract/DataBindingModifierType.cs index 2e01954bf..7f08e7a5a 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/Modifiers/Abstract/DataBindingModifierType.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/Modifiers/Abstract/DataBindingModifierType.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Artemis.Core.Services; namespace Artemis.Core { @@ -10,9 +9,6 @@ namespace Artemis.Core /// public abstract class DataBindingModifierType { - private IDataBindingService _dataBindingService; - private bool _registered; - /// /// Gets the plugin info this data binding modifier belongs to /// Note: Not set until after registering @@ -63,35 +59,5 @@ namespace Artemis.Core /// /// The modified value, must be a value of a type contained in public abstract object Apply(object currentValue, object parameterValue); - - internal void Register(PluginInfo pluginInfo, IDataBindingService dataBindingService) - { - if (_registered) - return; - - PluginInfo = pluginInfo; - _dataBindingService = dataBindingService; - - if (PluginInfo != Constants.CorePluginInfo) - PluginInfo.Instance.PluginDisabled += InstanceOnPluginDisabled; - - _registered = true; - } - - internal void Unsubscribe() - { - if (!_registered) - return; - - if (PluginInfo != Constants.CorePluginInfo) - PluginInfo.Instance.PluginDisabled -= InstanceOnPluginDisabled; - _registered = false; - } - - private void InstanceOnPluginDisabled(object sender, EventArgs e) - { - // The service will call Unsubscribe - _dataBindingService.RemoveModifierType(this); - } } } \ 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 index ce10a1bd0..4a4c43130 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs @@ -1,4 +1,5 @@ -using Artemis.Storage.Entities.Profile; +using System; +using Artemis.Storage.Entities.Profile; namespace Artemis.Core { @@ -9,7 +10,7 @@ namespace Artemis.Core /// initialize these for you. /// /// - public interface ILayerProperty : IStorageModel, IUpdateModel + public interface ILayerProperty : IStorageModel, IUpdateModel, IDisposable { /// /// Initializes the layer property diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs index acd4c9965..ed49633ad 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; -using Artemis.Core.Services; using Artemis.Storage.Entities.Profile; using Newtonsoft.Json; @@ -19,6 +18,8 @@ namespace Artemis.Core /// The type of property encapsulated in this layer property public abstract class LayerProperty : ILayerProperty { + private bool _disposed; + /// /// Creates a new instance of the class /// @@ -37,6 +38,9 @@ namespace Artemis.Core /// public void Update(double deltaTime) { + if (_disposed) + throw new ObjectDisposedException("LayerProperty"); + CurrentValue = BaseValue; UpdateKeyframes(); @@ -136,6 +140,9 @@ namespace Artemis.Core /// public void SetCurrentValue(T value, TimeSpan? time) { + if (_disposed) + throw new ObjectDisposedException("LayerProperty"); + if (time == null || !KeyframesEnabled || !KeyframesSupported) BaseValue = value; else @@ -159,6 +166,9 @@ namespace Artemis.Core /// public void ApplyDefaultValue() { + if (_disposed) + throw new ObjectDisposedException("LayerProperty"); + BaseValue = DefaultValue; CurrentValue = DefaultValue; } @@ -212,6 +222,9 @@ namespace Artemis.Core /// The keyframe to add public void AddKeyframe(LayerPropertyKeyframe keyframe) { + if (_disposed) + throw new ObjectDisposedException("LayerProperty"); + if (_keyframes.Contains(keyframe)) return; @@ -229,6 +242,9 @@ namespace Artemis.Core /// The keyframe to remove public LayerPropertyKeyframe CopyKeyframe(LayerPropertyKeyframe keyframe) { + if (_disposed) + throw new ObjectDisposedException("LayerProperty"); + var newKeyframe = new LayerPropertyKeyframe( keyframe.Value, keyframe.Position, @@ -246,6 +262,9 @@ namespace Artemis.Core /// The keyframe to remove public void RemoveKeyframe(LayerPropertyKeyframe keyframe) { + if (_disposed) + throw new ObjectDisposedException("LayerProperty"); + if (!_keyframes.Contains(keyframe)) return; @@ -260,6 +279,9 @@ namespace Artemis.Core /// public void ClearKeyframes() { + if (_disposed) + throw new ObjectDisposedException("LayerProperty"); + var keyframes = new List>(_keyframes); foreach (var layerPropertyKeyframe in keyframes) RemoveKeyframe(layerPropertyKeyframe); @@ -313,6 +335,9 @@ namespace Artemis.Core public DataBindingRegistration GetDataBindingRegistration(string propertyName) { + if (_disposed) + throw new ObjectDisposedException("LayerProperty"); + var match = _dataBindingRegistrations.FirstOrDefault(r => r is DataBindingRegistration registration && registration.Property.Name == propertyName); return (DataBindingRegistration) match; @@ -320,6 +345,9 @@ namespace Artemis.Core public void RegisterDataBindingProperty(Expression> propertyLambda, DataBindingConverter converter) { + if (_disposed) + throw new ObjectDisposedException("LayerProperty"); + // If the lambda references to itself, use the property info of public new T CurrentValue PropertyInfo propertyInfo; string path = null; @@ -353,6 +381,9 @@ namespace Artemis.Core /// The newly created data binding public DataBinding EnableDataBinding(DataBindingRegistration dataBindingRegistration) { + if (_disposed) + throw new ObjectDisposedException("LayerProperty"); + if (dataBindingRegistration.LayerProperty != this) throw new ArtemisCoreException("Cannot enable a data binding using a data binding registration of a different layer property"); @@ -368,6 +399,9 @@ namespace Artemis.Core /// The data binding to remove public void DisableDataBinding(DataBinding dataBinding) { + if (_disposed) + throw new ObjectDisposedException("LayerProperty"); + _dataBindings.Remove(dataBinding); } @@ -380,12 +414,6 @@ namespace Artemis.Core } } - internal void InitializeDataBindings(IDataModelService dataModelService, IDataBindingService dataBindingService) - { - foreach (var dataBinding in _dataBindings) - dataBinding.Initialize(dataModelService, dataBindingService); - } - #endregion #region Storage @@ -402,6 +430,9 @@ namespace Artemis.Core /// public void Initialize(RenderProfileElement profileElement, LayerPropertyGroup group, PropertyEntity entity, bool fromStorage, PropertyDescriptionAttribute description) { + if (_disposed) + throw new ObjectDisposedException("LayerProperty"); + _isInitialized = true; ProfileElement = profileElement ?? throw new ArgumentNullException(nameof(profileElement)); @@ -409,13 +440,16 @@ namespace Artemis.Core 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 (_disposed) + throw new ObjectDisposedException("LayerProperty"); + if (!_isInitialized) throw new ArtemisCoreException("Layer property is not yet initialized"); @@ -467,6 +501,9 @@ namespace Artemis.Core /// public void Save() { + if (_disposed) + throw new ObjectDisposedException("LayerProperty"); + if (!_isInitialized) throw new ArtemisCoreException("Layer property is not yet initialized"); @@ -550,5 +587,14 @@ namespace Artemis.Core } #endregion + + /// + public void Dispose() + { + _disposed = true; + + foreach (var dataBinding in _dataBindings) + dataBinding.Dispose(); + } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs index b6720b92f..bf423342c 100644 --- a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs +++ b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs @@ -6,7 +6,6 @@ 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 @@ -15,6 +14,7 @@ namespace Artemis.Core { private readonly List _layerProperties; private readonly List _layerPropertyGroups; + private bool _disposed; private bool _isHidden; protected LayerPropertyGroup() @@ -23,6 +23,9 @@ namespace Artemis.Core _layerPropertyGroups = new List(); } + /// + /// Gets the description of this group + /// public PropertyGroupDescriptionAttribute GroupDescription { get; internal set; } /// @@ -83,19 +86,30 @@ namespace Artemis.Core /// public ReadOnlyCollection LayerPropertyGroups => _layerPropertyGroups.AsReadOnly(); + #region IDisposable + /// public void Dispose() { + _disposed = true; DisableProperties(); + + foreach (var layerProperty in _layerProperties) + layerProperty.Dispose(); foreach (var layerPropertyGroup in _layerPropertyGroups) layerPropertyGroup.Dispose(); } + #endregion + /// /// Recursively gets all layer properties on this group and any subgroups /// public IReadOnlyCollection GetAllLayerProperties() { + if (_disposed) + throw new ObjectDisposedException("LayerPropertyGroup"); + if (!PropertiesInitialized) return new List(); @@ -240,7 +254,7 @@ namespace Artemis.Core return entity; } - + #region Events internal event EventHandler PropertyGroupUpdating; diff --git a/src/Artemis.Core/Models/Profile/Profile.cs b/src/Artemis.Core/Models/Profile/Profile.cs index e23874cfe..10806d755 100644 --- a/src/Artemis.Core/Models/Profile/Profile.cs +++ b/src/Artemis.Core/Models/Profile/Profile.cs @@ -34,7 +34,7 @@ namespace Artemis.Core UndoStack = new Stack(); RedoStack = new Stack(); - ApplyToProfile(); + Load(); } public ProfileModule Module { get; } @@ -86,7 +86,7 @@ namespace Artemis.Core return (Folder) Children.Single(); } - public void ApplyToProfile() + internal override void Load() { if (_disposed) throw new ObjectDisposedException("Profile"); diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs index bc3d86cb6..81fd839a1 100644 --- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs @@ -12,6 +12,12 @@ namespace Artemis.Core { public abstract class RenderProfileElement : ProfileElement { + protected RenderProfileElement() + { + LayerEffectStore.LayerEffectAdded += LayerEffectStoreOnLayerEffectAdded; + LayerEffectStore.LayerEffectRemoved += LayerEffectStoreOnLayerEffectRemoved; + } + internal void ApplyRenderElementDefaults() { MainSegmentLength = TimeSpan.FromSeconds(5); @@ -270,6 +276,7 @@ namespace Artemis.Core } } + internal void ActivateLayerEffect(BaseLayerEffect layerEffect) { _layerEffects.Add(layerEffect); @@ -285,6 +292,16 @@ namespace Artemis.Core OnLayerEffectsUpdated(); } + private void LayerEffectStoreOnLayerEffectRemoved(object? sender, LayerEffectStoreEvent e) + { + throw new NotImplementedException(); + } + + private void LayerEffectStoreOnLayerEffectAdded(object? sender, LayerEffectStoreEvent e) + { + ActivateEffects(); + } + #endregion #region Conditions diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index 49c575d8e..f51c51a69 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -17,21 +17,17 @@ namespace Artemis.Core.Services private readonly ILogger _logger; private readonly IPluginService _pluginService; private readonly IProfileRepository _profileRepository; - private readonly IRenderElementService _renderElementService; private readonly ISurfaceService _surfaceService; - internal ProfileService(ILogger logger, IPluginService pluginService, ISurfaceService surfaceService, IRenderElementService renderElementService, IProfileRepository profileRepository) + internal ProfileService(ILogger logger, IPluginService pluginService, ISurfaceService surfaceService, IProfileRepository profileRepository) { _logger = logger; _pluginService = pluginService; _surfaceService = surfaceService; - _renderElementService = renderElementService; _profileRepository = profileRepository; _surfaceService.ActiveSurfaceConfigurationSelected += OnActiveSurfaceConfigurationSelected; _surfaceService.SurfaceConfigurationUpdated += OnSurfaceConfigurationUpdated; - _pluginService.PluginEnabled += OnPluginToggled; - _pluginService.PluginDisabled += OnPluginToggled; } public JsonSerializerSettings MementoSettings { get; set; } = new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.All}; @@ -186,7 +182,7 @@ namespace Artemis.Core.Services profile.RedoStack.Push(memento); profile.ProfileEntity = JsonConvert.DeserializeObject(top, MementoSettings); - profile.ApplyToProfile(); + profile.Load(); InstantiateProfile(profile); } @@ -210,7 +206,7 @@ namespace Artemis.Core.Services profile.UndoStack.Push(memento); profile.ProfileEntity = JsonConvert.DeserializeObject(top, MementoSettings); - profile.ApplyToProfile(); + profile.Load(); InstantiateProfile(profile); _logger.Debug("Redo profile update - Success"); @@ -221,9 +217,6 @@ namespace Artemis.Core.Services public void InstantiateProfile(Profile profile) { profile.PopulateLeds(_surfaceService.ActiveSurface); - InitializeLayerProperties(profile); - InstantiateLayers(profile); - InstantiateFolders(profile); } public string ExportProfile(ProfileDescriptor profileDescriptor) @@ -268,67 +261,7 @@ namespace Artemis.Core.Services _profileRepository.Save(profileEntity); } } - - /// - /// Initializes the properties on the layers of the given profile - /// - /// - private void InitializeLayerProperties(Profile profile) - { - foreach (var layer in profile.GetAllLayers()) - { - if (!layer.General.PropertiesInitialized) - layer.General.Initialize(layer, "General.", Constants.CorePluginInfo); - if (!layer.Transform.PropertiesInitialized) - layer.Transform.Initialize(layer, "Transform.", Constants.CorePluginInfo); - } - } - - /// - /// Instantiates all plugin-related classes on the folders of the given profile - /// - private void InstantiateFolders(Profile profile) - { - foreach (var folder in profile.GetAllFolders()) - { - // Instantiate effects - _renderElementService.InstantiateLayerEffects(folder); - // Remove effects of plugins that are disabled - var disabledEffects = new List(folder.LayerEffects.Where(layerLayerEffect => !layerLayerEffect.PluginInfo.Enabled)); - foreach (var layerLayerEffect in disabledEffects) - _renderElementService.RemoveLayerEffect(layerLayerEffect); - - _renderElementService.InstantiateDisplayConditions(folder); - _renderElementService.InstantiateDataBindings(folder); - } - } - - /// - /// Instantiates all plugin-related classes on the layers of the given profile - /// - private void InstantiateLayers(Profile profile) - { - foreach (var layer in profile.GetAllLayers()) - { - // Instantiate brush - if (layer.LayerBrush == null) - _renderElementService.InstantiateLayerBrush(layer); - // Remove brush if plugin is disabled - else if (!layer.LayerBrush.PluginInfo.Enabled) - _renderElementService.DeactivateLayerBrush(layer); - - // Instantiate effects - _renderElementService.InstantiateLayerEffects(layer); - // Remove effects of plugins that are disabled - var disabledEffects = new List(layer.LayerEffects.Where(layerLayerEffect => !layerLayerEffect.PluginInfo.Enabled)); - foreach (var layerLayerEffect in disabledEffects) - _renderElementService.RemoveLayerEffect(layerLayerEffect); - - _renderElementService.InstantiateDisplayConditions(layer); - _renderElementService.InstantiateDataBindings(layer); - } - } - + /// /// Populates all missing LEDs on all currently active profiles /// @@ -339,21 +272,7 @@ namespace Artemis.Core.Services foreach (var profileModule in profileModules.Where(p => p.ActiveProfile != null).ToList()) profileModule.ActiveProfile.PopulateLeds(surface); } - - - /// - /// Instantiates all missing plugin-related classes on the profile trees of all currently active profiles - /// - private void ActiveProfilesInstantiatePlugins() - { - var profileModules = _pluginService.GetPluginsOfType(); - foreach (var profileModule in profileModules.Where(p => p.ActiveProfile != null).ToList()) - { - InstantiateLayers(profileModule.ActiveProfile); - InstantiateFolders(profileModule.ActiveProfile); - } - } - + #region Event handlers private void OnActiveSurfaceConfigurationSelected(object sender, SurfaceConfigurationEventArgs e) @@ -366,15 +285,7 @@ namespace Artemis.Core.Services if (e.Surface.IsActive) ActiveProfilesPopulateLeds(e.Surface); } - - private void OnPluginToggled(object sender, PluginEventArgs e) - { - if (e.PluginInfo.Instance is LayerBrushProvider) - ActiveProfilesInstantiatePlugins(); - if (e.PluginInfo.Instance is LayerEffectProvider) - ActiveProfilesInstantiatePlugins(); - } - + #endregion } } \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.LayerBrushes.Color/PropertyGroups/ColorBrushProperties.cs b/src/Plugins/Artemis.Plugins.LayerBrushes.Color/PropertyGroups/ColorBrushProperties.cs index b840bfc07..beeaec69a 100644 --- a/src/Plugins/Artemis.Plugins.LayerBrushes.Color/PropertyGroups/ColorBrushProperties.cs +++ b/src/Plugins/Artemis.Plugins.LayerBrushes.Color/PropertyGroups/ColorBrushProperties.cs @@ -37,21 +37,26 @@ namespace Artemis.Plugins.LayerBrushes.Color.PropertyGroups protected override void EnableProperties() { - GradientType.BaseValueChanged += OnBaseValueChanged; + GradientType.BaseValueChanged += GradientTypeOnBaseValueChanged; if (ProfileElement is Layer layer) - layer.General.ResizeMode.BaseValueChanged += OnBaseValueChanged; + layer.General.ResizeMode.BaseValueChanged += ResizeModeOnBaseValueChanged; UpdateVisibility(); } protected override void DisableProperties() { - GradientType.BaseValueChanged -= OnBaseValueChanged; + GradientType.BaseValueChanged -= GradientTypeOnBaseValueChanged; if (ProfileElement is Layer layer) - layer.General.ResizeMode.BaseValueChanged -= OnBaseValueChanged; + layer.General.ResizeMode.BaseValueChanged -= ResizeModeOnBaseValueChanged; } - private void OnBaseValueChanged(object sender, LayerPropertyEventArgs e) + private void GradientTypeOnBaseValueChanged(object sender, LayerPropertyEventArgs e) + { + UpdateVisibility(); + } + + private void ResizeModeOnBaseValueChanged(object sender, LayerPropertyEventArgs e) { UpdateVisibility(); } diff --git a/src/Plugins/Artemis.Plugins.LayerBrushes.Color/PropertyGroups/RadialGradientProperties.cs b/src/Plugins/Artemis.Plugins.LayerBrushes.Color/PropertyGroups/RadialGradientProperties.cs index 6af1093b9..21afb4bf5 100644 --- a/src/Plugins/Artemis.Plugins.LayerBrushes.Color/PropertyGroups/RadialGradientProperties.cs +++ b/src/Plugins/Artemis.Plugins.LayerBrushes.Color/PropertyGroups/RadialGradientProperties.cs @@ -18,7 +18,7 @@ namespace Artemis.Plugins.LayerBrushes.Color.PropertyGroups protected override void EnableProperties() { if (ProfileElement is Layer layer) - layer.General.ResizeMode.BaseValueChanged += OnBaseValueChanged; + layer.General.ResizeMode.BaseValueChanged += ResizeModeOnBaseValueChanged; UpdateVisibility(); } @@ -26,10 +26,10 @@ namespace Artemis.Plugins.LayerBrushes.Color.PropertyGroups protected override void DisableProperties() { if (ProfileElement is Layer layer) - layer.General.ResizeMode.BaseValueChanged -= OnBaseValueChanged; + layer.General.ResizeMode.BaseValueChanged -= ResizeModeOnBaseValueChanged; } - private void OnBaseValueChanged(object sender, LayerPropertyEventArgs e) + private void ResizeModeOnBaseValueChanged(object sender, LayerPropertyEventArgs e) { UpdateVisibility(); } From c07ea09c9d6a1526db4041772961edde4de261ae Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 10 Sep 2020 19:56:39 +0200 Subject: [PATCH 05/12] Core - Streamlined public parts of profile creation UI - Started adjusting the VMs for the layer refactor --- .../Abstract/DisplayConditionPart.cs | 2 +- .../Conditions/DisplayConditionGroup.cs | 4 +- .../Conditions/DisplayConditionList.cs | 4 +- .../DisplayConditionListPredicate.cs | 55 +++++- .../Conditions/DisplayConditionPredicate.cs | 36 ++-- src/Artemis.Core/Models/Profile/Folder.cs | 36 ++-- src/Artemis.Core/Models/Profile/Layer.cs | 56 ++++-- src/Artemis.Core/Models/Profile/Profile.cs | 8 +- .../Models/Profile/ProfileElement.cs | 5 +- .../Models/Profile/RenderProfileElement.cs | 30 +++- .../LayerEffects/Internal/BaseLayerEffect.cs | 2 + .../LayerEffects/LayerEffectDescriptor.cs | 18 +- .../Placeholder/PlaceholderLayerEffect.cs | 58 ++++++ .../PlaceholderLayerEffectDescriptor.cs | 11 ++ src/Artemis.Core/Stores/LayerBrushStore.cs | 3 +- .../Interfaces/IProfileEditorService.cs | 9 +- .../Services/ProfileEditorService.cs | 11 ++ .../Ninject/Factories/IVMFactory.cs | 31 ++-- src/Artemis.UI/Ninject/UiModule.cs | 2 +- .../Abstract/DisplayConditionViewModel.cs | 29 +-- .../DisplayConditionGroupView.xaml | 2 +- .../DisplayConditionGroupViewModel.cs | 40 ++--- .../DisplayConditionListPredicateViewModel.cs | 25 ++- .../DisplayConditionListView.xaml | 2 +- .../DisplayConditionListViewModel.cs | 31 ++-- .../DisplayConditionPredicateViewModel.cs | 27 ++- .../DisplayConditionsView.xaml | 2 +- .../DisplayConditionsViewModel.cs | 24 +-- ...del.cs => IProfileEditorPanelViewModel.cs} | 2 +- .../Abstract/LayerPropertyBaseViewModel.cs | 36 ---- .../LayerEffects/EffectsViewModel.cs | 28 +-- .../LayerPropertiesViewModel.cs | 3 +- .../LayerPropertyGroupViewModel.cs | 166 +++--------------- .../LayerProperties/LayerPropertyViewModel.cs | 136 ++------------ .../Rails/LayerPropertyTimelineViewModel.cs | 26 +++ .../Timeline/Rails/TimelineGroupViewModel.cs | 17 ++ ...w.xaml => LayerPropertyGroupTreeView.xaml} | 52 +++--- ....cs => LayerPropertyGroupTreeViewModel.cs} | 57 ++++-- ...tyView.xaml => LayerPropertyTreeView.xaml} | 14 +- .../Tree/LayerPropertyTreeViewModel.cs | 93 ++++++++++ .../Tree/TreePropertyViewModel.cs | 89 ---------- .../LayerProperties/Tree/TreeView.xaml | 10 +- .../LayerProperties/Tree/TreeViewModel.cs | 4 +- .../ProfileEditor/ProfileEditorViewModel.cs | 4 +- .../ProfileTree/ProfileTreeView.xaml | 6 +- .../ProfileTree/ProfileTreeViewModel.cs | 38 ++-- .../ProfileTree/TreeItem/FolderViewModel.cs | 30 +--- .../ProfileTree/TreeItem/LayerViewModel.cs | 10 +- .../ProfileTree/TreeItem/TreeItemViewModel.cs | 99 ++++------- .../Visualization/ProfileDeviceView.xaml | 60 ------- .../Visualization/ProfileDeviceViewModel.cs | 96 ---------- .../Visualization/ProfileLedView.xaml | 93 ---------- .../Visualization/ProfileLedViewModel.cs | 148 ---------------- .../Visualization/ProfileViewModel.cs | 2 +- .../LayerBrushSettingsWindowView.xaml | 2 +- .../LayerBrushSettingsWindowViewModel.cs | 2 +- .../LayerEffectSettingsWindowView.xaml | 2 +- .../LayerEffectSettingsWindowViewModel.cs | 2 +- 58 files changed, 694 insertions(+), 1196 deletions(-) create mode 100644 src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffect.cs create mode 100644 src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffectDescriptor.cs rename src/Artemis.UI/Screens/ProfileEditor/{ProfileEditorPanelViewModel.cs => IProfileEditorPanelViewModel.cs} (55%) delete mode 100644 src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Abstract/LayerPropertyBaseViewModel.cs create mode 100644 src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Rails/LayerPropertyTimelineViewModel.cs create mode 100644 src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Rails/TimelineGroupViewModel.cs rename src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/{TreePropertyGroupView.xaml => LayerPropertyGroupTreeView.xaml} (71%) rename src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/{TreePropertyGroupViewModel.cs => LayerPropertyGroupTreeViewModel.cs} (69%) rename src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/{TreePropertyView.xaml => LayerPropertyTreeView.xaml} (78%) create mode 100644 src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/LayerPropertyTreeViewModel.cs delete mode 100644 src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs delete mode 100644 src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileDeviceView.xaml delete mode 100644 src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileDeviceViewModel.cs delete mode 100644 src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileLedView.xaml delete mode 100644 src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileLedViewModel.cs rename src/Artemis.UI/Screens/ProfileEditor/{ => Windows}/LayerBrushSettingsWindowView.xaml (97%) rename src/Artemis.UI/Screens/ProfileEditor/{ => Windows}/LayerBrushSettingsWindowViewModel.cs (92%) rename src/Artemis.UI/Screens/ProfileEditor/{ => Windows}/LayerEffectSettingsWindowView.xaml (97%) rename src/Artemis.UI/Screens/ProfileEditor/{ => Windows}/LayerEffectSettingsWindowViewModel.cs (92%) diff --git a/src/Artemis.Core/Models/Profile/Conditions/Abstract/DisplayConditionPart.cs b/src/Artemis.Core/Models/Profile/Conditions/Abstract/DisplayConditionPart.cs index 72ca6a7c4..f751046d0 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/Abstract/DisplayConditionPart.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Abstract/DisplayConditionPart.cs @@ -61,7 +61,7 @@ namespace Artemis.Core /// public abstract bool EvaluateObject(object target); - internal abstract void ApplyToEntity(); + internal abstract void Save(); internal abstract DisplayConditionPartEntity GetEntity(); #region IDisposable diff --git a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionGroup.cs b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionGroup.cs index cc4cc7e49..055a40b0b 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionGroup.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionGroup.cs @@ -105,14 +105,14 @@ namespace Artemis.Core }; } - internal override void ApplyToEntity() + internal override void Save() { Entity.BooleanOperator = (int) BooleanOperator; Entity.Children.Clear(); Entity.Children.AddRange(Children.Select(c => c.GetEntity())); foreach (var child in Children) - child.ApplyToEntity(); + child.Save(); } internal override DisplayConditionPartEntity GetEntity() diff --git a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionList.cs b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionList.cs index 155ae3489..5ecbc607c 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionList.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionList.cs @@ -116,7 +116,7 @@ namespace Artemis.Core CompiledListAccessor = lambda.Compile(); } - internal override void ApplyToEntity() + internal override void Save() { // Target list if (ListDataModel != null) @@ -132,7 +132,7 @@ namespace Artemis.Core Entity.Children.Clear(); Entity.Children.AddRange(Children.Select(c => c.GetEntity())); foreach (var child in Children) - child.ApplyToEntity(); + child.Save(); } internal override DisplayConditionPartEntity GetEntity() diff --git a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionListPredicate.cs b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionListPredicate.cs index 41c5890df..ef93a992b 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionListPredicate.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionListPredicate.cs @@ -190,22 +190,31 @@ namespace Artemis.Core return result; } - internal override void ApplyToEntity() + internal override void Save() { Entity.PredicateType = (int) PredicateType; - Entity.ListDataModelGuid = ListDataModel?.PluginInfo?.Guid; - Entity.ListPropertyPath = ListPropertyPath; + if (ListDataModel != null) + { + Entity.ListDataModelGuid = ListDataModel.PluginInfo.Guid; + Entity.ListPropertyPath = ListPropertyPath; + } Entity.LeftPropertyPath = LeftPropertyPath; Entity.RightPropertyPath = RightPropertyPath; Entity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue); - Entity.OperatorPluginGuid = Operator?.PluginInfo?.Guid; - Entity.OperatorType = Operator?.GetType().Name; + if (Operator != null) + { + Entity.OperatorPluginGuid = Operator.PluginInfo.Guid; + Entity.OperatorType = Operator.GetType().Name; + } } - internal void Initialize() + private void Initialize() { + ConditionOperatorStore.ConditionOperatorAdded += ConditionOperatorStoreOnConditionOperatorAdded; + ConditionOperatorStore.ConditionOperatorRemoved += ConditionOperatorStoreOnConditionOperatorRemoved; + // Left side if (Entity.LeftPropertyPath != null && ListContainsInnerPath(Entity.LeftPropertyPath)) UpdateLeftSide(Entity.LeftPropertyPath); @@ -222,7 +231,7 @@ namespace Artemis.Core if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPropertyPath != null) { if (ListContainsInnerPath(Entity.RightPropertyPath)) - UpdateLeftSide(Entity.LeftPropertyPath); + UpdateRightSideDynamic(Entity.RightPropertyPath); } // Right side static else if (PredicateType == ProfileRightSideType.Static && Entity.RightStaticValue != null) @@ -384,5 +393,37 @@ namespace Artemis.Core Expression.Property ); } + + #region Event handlers + + private void ConditionOperatorStoreOnConditionOperatorAdded(object sender, ConditionOperatorStoreEvent e) + { + var conditionOperator = e.Registration.ConditionOperator; + if (Entity.OperatorPluginGuid == conditionOperator.PluginInfo.Guid && Entity.OperatorType == conditionOperator.GetType().Name) + UpdateOperator(conditionOperator); + } + + private void ConditionOperatorStoreOnConditionOperatorRemoved(object sender, ConditionOperatorStoreEvent e) + { + if (e.Registration.ConditionOperator != Operator) + return; + Operator = null; + CompiledListPredicate = null; + } + + #endregion + + #region IDisposable + + /// + protected override void Dispose(bool disposing) + { + ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded; + ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved; + + base.Dispose(disposing); + } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs index 53d70de8a..a86f57dc1 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs @@ -232,7 +232,7 @@ namespace Artemis.Core return $"[Static] {LeftPropertyPath} {Operator.Description} {RightStaticValue}"; } - internal override void ApplyToEntity() + internal override void Save() { Entity.PredicateType = (int) PredicateType; Entity.LeftDataModelGuid = LeftDataModel?.PluginInfo?.Guid; @@ -250,6 +250,8 @@ namespace Artemis.Core { DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded; DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved; + ConditionOperatorStore.ConditionOperatorAdded += ConditionOperatorStoreOnConditionOperatorAdded; + ConditionOperatorStore.ConditionOperatorRemoved += ConditionOperatorStoreOnConditionOperatorRemoved; // Left side if (Entity.LeftDataModelGuid != null) @@ -415,19 +417,6 @@ namespace Artemis.Core ); } - private Expression CreateListAccessor(DataModel dataModel, string path, ParameterExpression listParameter) - { - var listType = dataModel.GetListTypeInPath(path); - if (listType == null) - throw new ArtemisCoreException($"Cannot create a list accessor at path {path} because the path does not contain a list"); - - path = dataModel.GetListInnerPath(path); - return path.Split('.').Aggregate( - Expression.Convert(listParameter, listType), // Cast to the appropriate type - Expression.Property - ); - } - #region Event handlers private void DataModelStoreOnDataModelAdded(object sender, DataModelStoreEvent e) @@ -454,6 +443,23 @@ namespace Artemis.Core } } + private void ConditionOperatorStoreOnConditionOperatorAdded(object sender, ConditionOperatorStoreEvent e) + { + var conditionOperator = e.Registration.ConditionOperator; + if (Entity.OperatorPluginGuid == conditionOperator.PluginInfo.Guid && Entity.OperatorType == conditionOperator.GetType().Name) + UpdateOperator(conditionOperator); + } + + private void ConditionOperatorStoreOnConditionOperatorRemoved(object sender, ConditionOperatorStoreEvent e) + { + if (e.Registration.ConditionOperator != Operator) + return; + + Operator = null; + CompiledStaticPredicate = null; + CompiledDynamicPredicate = null; + } + #endregion /// @@ -461,6 +467,8 @@ namespace Artemis.Core { DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded; DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved; + ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded; + ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved; base.Dispose(disposing); } diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs index 1436f7bcf..4491b8143 100644 --- a/src/Artemis.Core/Models/Profile/Folder.cs +++ b/src/Artemis.Core/Models/Profile/Folder.cs @@ -8,24 +8,35 @@ using SkiaSharp; namespace Artemis.Core { + /// + /// Represents a folder in a + /// public sealed class Folder : RenderProfileElement { private SKBitmap _folderBitmap; - public Folder(Profile profile, ProfileElement parent, string name) + /// + /// Creates a new instance of the class and adds itself to the child collection of the provided + /// + /// + /// The parent of the folder + /// The name of the folder + public Folder(ProfileElement parent, string name) { FolderEntity = new FolderEntity(); EntityId = Guid.NewGuid(); - Profile = profile; - Parent = parent; + Parent = parent ?? throw new ArgumentNullException(nameof(parent)); + Profile = Parent.Profile; Name = name; Enabled = true; DisplayContinuously = true; _layerEffects = new List(); _expandedPropertyGroups = new List(); + ApplyRenderElementDefaults(); + Parent.AddChild(this); } internal Folder(Profile profile, ProfileElement parent, FolderEntity folderEntity) @@ -182,22 +193,7 @@ namespace Artemis.Core canvas.Restore(); } - - /// - /// Adds a new folder to the bottom of this folder - /// - /// - /// - public Folder AddFolder(string name) - { - if (_disposed) - throw new ObjectDisposedException("Folder"); - - var folder = new Folder(Profile, this, name) {Order = Children.LastOrDefault()?.Order ?? 1}; - AddChild(folder); - return folder; - } - + /// public override void AddChild(ProfileElement child, int? order = null) { @@ -296,7 +292,7 @@ namespace Artemis.Core // Conditions RenderElementEntity.RootDisplayCondition = DisplayConditionGroup?.Entity; - DisplayConditionGroup?.ApplyToEntity(); + DisplayConditionGroup?.Save(); SaveRenderElement(); } diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index b1d411e9c..fc8dbb7c6 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -11,8 +11,7 @@ using SkiaSharp; namespace Artemis.Core { /// - /// Represents a layer on a profile. To create new layers use the by injecting - /// into your code + /// Represents a layer in a /// public sealed class Layer : RenderProfileElement { @@ -23,13 +22,19 @@ namespace Artemis.Core private List _leds; private LayerTransformProperties _transform; - internal Layer(Profile profile, ProfileElement parent, string name) + /// + /// Creates a new instance of the class and adds itself to the child collection of the provided + /// + /// + /// The parent of the layer + /// The name of the layer + public Layer(ProfileElement parent, string name) { LayerEntity = new LayerEntity(); EntityId = Guid.NewGuid(); - Profile = profile; - Parent = parent; + Parent = parent ?? throw new ArgumentNullException(nameof(parent)); + Profile = Parent.Profile; Name = name; Enabled = true; DisplayContinuously = true; @@ -40,10 +45,10 @@ namespace Artemis.Core _leds = new List(); _expandedPropertyGroups = new List(); - InitializeDefaultGroups(); - - parent.AddChild(this); + Initialize(); ApplyRenderElementDefaults(); + + Parent.AddChild(this); } internal Layer(Profile profile, ProfileElement parent, LayerEntity layerEntity) @@ -58,8 +63,7 @@ namespace Artemis.Core _leds = new List(); _expandedPropertyGroups = new List(); - InitializeDefaultGroups(); - + Initialize(); Load(); } @@ -114,6 +118,8 @@ namespace Artemis.Core return $"[Layer] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}"; } + #region IDisposable + /// protected override void Dispose(bool disposing) { @@ -133,8 +139,13 @@ namespace Artemis.Core _transform?.Dispose(); } - private void InitializeDefaultGroups() + #endregion + + private void Initialize() { + LayerBrushStore.LayerBrushAdded += LayerBrushStoreOnLayerBrushAdded; + LayerBrushStore.LayerBrushRemoved += LayerBrushStoreOnLayerBrushRemoved; + // Layers have two hardcoded property groups, instantiate them General.Initialize(this, "General.", Constants.CorePluginInfo); Transform.Initialize(this, "Transform.", Constants.CorePluginInfo); @@ -191,7 +202,7 @@ namespace Artemis.Core // Conditions RenderElementEntity.RootDisplayCondition = DisplayConditionGroup?.Entity; - DisplayConditionGroup?.ApplyToEntity(); + DisplayConditionGroup?.Save(); SaveRenderElement(); } @@ -711,6 +722,27 @@ namespace Artemis.Core #endregion + #region Event handlers + + private void LayerBrushStoreOnLayerBrushRemoved(object sender, LayerBrushStoreEvent e) + { + if (LayerBrush.Descriptor == e.Registration.LayerBrushDescriptor) + DeactivateLayerBrush(); + } + + private void LayerBrushStoreOnLayerBrushAdded(object sender, LayerBrushStoreEvent e) + { + if (LayerBrush != null) + return; + + var current = General.BrushReference.CurrentValue; + if (e.Registration.Plugin.PluginInfo.Guid == current.BrushPluginGuid && + e.Registration.LayerBrushDescriptor.LayerBrushType.Name == current.BrushType) + ActivateLayerBrush(); + } + + #endregion + #region Events public event EventHandler RenderPropertiesUpdated; diff --git a/src/Artemis.Core/Models/Profile/Profile.cs b/src/Artemis.Core/Models/Profile/Profile.cs index 10806d755..583c10119 100644 --- a/src/Artemis.Core/Models/Profile/Profile.cs +++ b/src/Artemis.Core/Models/Profile/Profile.cs @@ -16,17 +16,19 @@ namespace Artemis.Core ProfileEntity = new ProfileEntity(); EntityId = Guid.NewGuid(); + Profile = this; Module = module; Name = name; UndoStack = new Stack(); RedoStack = new Stack(); - AddChild(new Folder(this, this, "Root folder")); + var _ = new Folder(this, "Root folder"); Save(); } internal Profile(ProfileModule module, ProfileEntity profileEntity) { + Profile = this; ProfileEntity = profileEntity; EntityId = profileEntity.Id; @@ -103,7 +105,9 @@ namespace Artemis.Core // Populate the profile starting at the root, the rest is populated recursively var rootFolder = ProfileEntity.Folders.FirstOrDefault(f => f.ParentId == EntityId); if (rootFolder == null) - AddChild(new Folder(this, this, "Root folder")); + { + var _ = new Folder(this, "Root folder"); + } else AddChild(new Folder(this, this, rootFolder)); } diff --git a/src/Artemis.Core/Models/Profile/ProfileElement.cs b/src/Artemis.Core/Models/Profile/ProfileElement.cs index 0a2ee39aa..c91bcd2fe 100644 --- a/src/Artemis.Core/Models/Profile/ProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/ProfileElement.cs @@ -110,9 +110,12 @@ namespace Artemis.Core { if (_disposed) throw new ObjectDisposedException(GetType().Name); - + lock (ChildrenList) { + if (ChildrenList.Contains(child)) + return; + // Add to the end of the list if (order == null) { diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs index 81fd839a1..20c938211 100644 --- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using Artemis.Core.LayerEffects; +using Artemis.Core.LayerEffects.Placeholder; using Artemis.Core.Properties; using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile.Abstract; @@ -49,7 +50,7 @@ namespace Artemis.Core { Id = layerEffect.EntityId, PluginGuid = layerEffect.PluginInfo.Guid, - EffectType = layerEffect.GetType().Name, + EffectType = layerEffect.GetEffectTypeName(), Name = layerEffect.Name, Enabled = layerEffect.Enabled, HasBeenRenamed = layerEffect.HasBeenRenamed, @@ -268,11 +269,29 @@ namespace Artemis.Core { foreach (var layerEffectEntity in RenderElementEntity.LayerEffects) { - if (_layerEffects.Any(e => e.EntityId == layerEffectEntity.Id)) + // If there is a non-placeholder existing effect, skip this entity + var existing = _layerEffects.FirstOrDefault(e => e.EntityId == layerEffectEntity.Id); + if (existing != null && !existing.Descriptor.IsPlaceHolder) continue; var descriptor = LayerEffectStore.Get(layerEffectEntity.PluginGuid, layerEffectEntity.EffectType)?.LayerEffectDescriptor; - descriptor?.CreateInstance(this, layerEffectEntity); + if (descriptor != null) + { + // If a descriptor is found but there is an existing placeholder, remove the placeholder + if (existing != null) + { + _layerEffects.Remove(existing); + existing.Dispose(); + } + // Create an instance with the descriptor + descriptor.CreateInstance(this, layerEffectEntity); + } + else if (existing == null) + { + // If no descriptor was found and there was no existing placeholder, create a placeholder + descriptor = PlaceholderLayerEffectDescriptor.Create(); + descriptor.CreateInstance(this, layerEffectEntity); + } } } @@ -297,9 +316,10 @@ namespace Artemis.Core throw new NotImplementedException(); } - private void LayerEffectStoreOnLayerEffectAdded(object? sender, LayerEffectStoreEvent e) + private void LayerEffectStoreOnLayerEffectAdded(object sender, LayerEffectStoreEvent e) { - ActivateEffects(); + if (RenderElementEntity.LayerEffects.Any(ef => ef.PluginGuid == e.Registration.Plugin.PluginInfo.Guid)) + ActivateEffects(); } #endregion diff --git a/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs b/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs index f928cbab8..0607a7f24 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs +++ b/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs @@ -148,5 +148,7 @@ namespace Artemis.Core.LayerEffects // Not only is this needed to initialize properties on the layer effects, it also prevents implementing anything // but LayerEffect outside the core internal abstract void Initialize(); + + internal virtual string GetEffectTypeName() => GetType().Name; } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs b/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs index 47d95d396..808869b6f 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs +++ b/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using Artemis.Core.LayerBrushes; using Artemis.Storage.Entities.Profile; using Ninject; @@ -51,6 +50,11 @@ namespace Artemis.Core.LayerEffects /// internal IKernel Kernel { get; set; } + /// + /// Gets a boolean indicating if this descriptor is a placeholder for a missing plugin + /// + public bool IsPlaceHolder { get; internal set; } + /// /// Creates an instance of the described effect and applies it to the render element /// @@ -60,6 +64,12 @@ namespace Artemis.Core.LayerEffects if (renderElement.LayerEffects.Any(e => e.EntityId == entity.Id)) return; + if (IsPlaceHolder) + { + CreatePlaceHolderInstance(renderElement, entity); + return; + } + var effect = (BaseLayerEffect) Kernel.Get(LayerEffectType); effect.ProfileElement = renderElement; effect.EntityId = entity.Id; @@ -73,5 +83,11 @@ namespace Artemis.Core.LayerEffects renderElement.ActivateLayerEffect(effect); } + + private void CreatePlaceHolderInstance(RenderProfileElement renderElement, LayerEffectEntity entity) + { + var effect = new PlaceholderLayerEffect(entity) {ProfileElement = renderElement, Descriptor = this}; + renderElement.ActivateLayerEffect(effect); + } } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffect.cs b/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffect.cs new file mode 100644 index 000000000..067417d0d --- /dev/null +++ b/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffect.cs @@ -0,0 +1,58 @@ +using Artemis.Storage.Entities.Profile; +using SkiaSharp; + +namespace Artemis.Core.LayerEffects +{ + /// + /// Represents a layer effect that could not be loaded due to a missing plugin + /// + public class PlaceholderLayerEffect : BaseLayerEffect + { + internal PlaceholderLayerEffect(LayerEffectEntity originalEntity) + { + OriginalEntity = originalEntity; + + EntityId = OriginalEntity.Id; + Order = OriginalEntity.Order; + Name = OriginalEntity.Name; + Enabled = OriginalEntity.Enabled; + HasBeenRenamed = OriginalEntity.HasBeenRenamed; + } + + internal LayerEffectEntity OriginalEntity { get; } + + /// + public override void EnableLayerEffect() + { + } + + /// + public override void DisableLayerEffect() + { + } + + /// + public override void Update(double deltaTime) + { + } + + /// + public override void PreProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath renderBounds, SKPaint paint) + { + } + + /// + public override void PostProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath renderBounds, SKPaint paint) + { + } + + internal override string GetEffectTypeName() + { + return OriginalEntity.EffectType; + } + + internal override void Initialize() + { + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffectDescriptor.cs b/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffectDescriptor.cs new file mode 100644 index 000000000..b34583a0f --- /dev/null +++ b/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffectDescriptor.cs @@ -0,0 +1,11 @@ +namespace Artemis.Core.LayerEffects.Placeholder +{ + internal static class PlaceholderLayerEffectDescriptor + { + public static LayerEffectDescriptor Create() + { + var descriptor = new LayerEffectDescriptor("Missing effect", "This effect could not be loaded", "FileQuestion", null, null) {IsPlaceHolder = true}; + return descriptor; + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Stores/LayerBrushStore.cs b/src/Artemis.Core/Stores/LayerBrushStore.cs index 75af208b0..9dd77dc9b 100644 --- a/src/Artemis.Core/Stores/LayerBrushStore.cs +++ b/src/Artemis.Core/Stores/LayerBrushStore.cs @@ -51,7 +51,8 @@ namespace Artemis.Core { lock (Registrations) { - return Registrations.FirstOrDefault(d => d.Plugin.PluginInfo.Guid == pluginGuid && d.LayerBrushDescriptor.LayerBrushType.Name == typeName); + return Registrations.FirstOrDefault(d => d.Plugin.PluginInfo.Guid == pluginGuid && + d.LayerBrushDescriptor.LayerBrushType.Name == typeName); } } diff --git a/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs b/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs index 92df0c90c..b4185660e 100644 --- a/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs @@ -10,7 +10,7 @@ namespace Artemis.UI.Shared.Services { Profile SelectedProfile { get; } RenderProfileElement SelectedProfileElement { get; } - BaseLayerProperty SelectedDataBinding { get; } + ILayerProperty SelectedDataBinding { get; } TimeSpan CurrentTime { get; set; } int PixelsPerSecond { get; set; } IReadOnlyList RegisteredPropertyEditors { get; } @@ -19,7 +19,7 @@ namespace Artemis.UI.Shared.Services void UpdateSelectedProfile(); void ChangeSelectedProfileElement(RenderProfileElement profileElement); void UpdateSelectedProfileElement(); - void ChangeSelectedDataBinding(BaseLayerProperty layerProperty); + void ChangeSelectedDataBinding(ILayerProperty layerProperty); void UpdateProfilePreview(); bool UndoUpdateProfile(); bool RedoUpdateProfile(); @@ -88,5 +88,10 @@ namespace Artemis.UI.Shared.Services /// A keyframe to exclude during keyframe snapping /// TimeSpan SnapToTimeline(TimeSpan time, TimeSpan tolerance, bool snapToSegments, bool snapToCurrentTime, bool snapToKeyframes, BaseLayerPropertyKeyframe excludedKeyframe = null); + + /// + /// If a matching registration is found, creates a new supporting + /// + PropertyInputViewModel CreatePropertyInputViewModel(LayerProperty layerProperty); } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs index ac437396b..bb651f9bd 100644 --- a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs @@ -5,6 +5,7 @@ using Artemis.Core; using Artemis.Core.Modules; using Artemis.Core.Services; using Ninject; +using Ninject.Parameters; using Serilog; using Stylet; @@ -269,6 +270,16 @@ namespace Artemis.UI.Shared.Services return time; } + public PropertyInputViewModel CreatePropertyInputViewModel(LayerProperty layerProperty) + { + var registration = RegisteredPropertyEditors.FirstOrDefault(r => r.SupportedType == typeof(T)); + if (registration == null) + return null; + + var parameter = new ConstructorArgument("layerProperty", layerProperty); + return (PropertyInputViewModel) Kernel.Get(registration.ViewModelType, parameter); + } + public ProfileModule GetCurrentModule() { return SelectedProfile?.Module; diff --git a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs index 68df7dd44..04c3cecef 100644 --- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -1,13 +1,10 @@ -using System.Reflection; -using Artemis.Core; +using Artemis.Core; using Artemis.Core.Modules; using Artemis.UI.Screens.Modules; using Artemis.UI.Screens.Modules.Tabs; using Artemis.UI.Screens.ProfileEditor; using Artemis.UI.Screens.ProfileEditor.DisplayConditions; -using Artemis.UI.Screens.ProfileEditor.DisplayConditions.Abstract; using Artemis.UI.Screens.ProfileEditor.LayerProperties; -using Artemis.UI.Screens.ProfileEditor.LayerProperties.Abstract; using Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings; using Artemis.UI.Screens.ProfileEditor.LayerProperties.LayerEffects; using Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline; @@ -19,7 +16,6 @@ using Artemis.UI.Screens.Settings.Debug; using Artemis.UI.Screens.Settings.Tabs.Devices; using Artemis.UI.Screens.Settings.Tabs.Plugins; using Stylet; -using Module = Artemis.Core.Modules.Module; namespace Artemis.UI.Ninject.Factories { @@ -46,15 +42,10 @@ namespace Artemis.UI.Ninject.Factories DeviceDebugViewModel Create(ArtemisDevice device); } - public interface IFolderVmFactory : IVmFactory + public interface IProfileTreeVmFactory : IVmFactory { - FolderViewModel Create(ProfileElement folder); - FolderViewModel Create(TreeItemViewModel parent, ProfileElement folder); - } - - public interface ILayerVmFactory : IVmFactory - { - LayerViewModel Create(TreeItemViewModel parent, ProfileElement folder); + FolderViewModel FolderViewModel(ProfileElement folder); + LayerViewModel LayerViewModel(ProfileElement layer); } public interface IProfileLayerVmFactory : IVmFactory @@ -72,10 +63,10 @@ namespace Artemis.UI.Ninject.Factories public interface IDisplayConditionsVmFactory : IVmFactory { - DisplayConditionGroupViewModel DisplayConditionGroupViewModel(DisplayConditionGroup displayConditionGroup, DisplayConditionViewModel parent, bool isListGroup); - DisplayConditionListViewModel DisplayConditionListViewModel(DisplayConditionList displayConditionList, DisplayConditionViewModel parent); - DisplayConditionPredicateViewModel DisplayConditionPredicateViewModel(DisplayConditionPredicate displayConditionPredicate, DisplayConditionViewModel parent); - DisplayConditionListPredicateViewModel DisplayConditionListPredicateViewModel(DisplayConditionListPredicate displayConditionListPredicate, DisplayConditionViewModel parent); + DisplayConditionGroupViewModel DisplayConditionGroupViewModel(DisplayConditionGroup displayConditionGroup, bool isListGroup); + DisplayConditionListViewModel DisplayConditionListViewModel(DisplayConditionList displayConditionList); + DisplayConditionPredicateViewModel DisplayConditionPredicateViewModel(DisplayConditionPredicate displayConditionPredicate); + DisplayConditionListPredicateViewModel DisplayConditionListPredicateViewModel(DisplayConditionListPredicate displayConditionListPredicate); } public interface IDataBindingsVmFactory : IVmFactory @@ -87,11 +78,13 @@ namespace Artemis.UI.Ninject.Factories public interface ILayerPropertyVmFactory : IVmFactory { + LayerPropertyViewModel LayerPropertyViewModel(ILayerProperty layerProperty); + LayerPropertyGroupViewModel LayerPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup); + LayerPropertyTreeViewModel LayerPropertyGroupViewModel(LayerProperty layerProperty); + LayerPropertyGroupTreeViewModel LayerPropertyGroupTreeViewModel(LayerPropertyGroupViewModel layerPropertyGroupViewModel); LayerPropertyGroupViewModel LayerPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, PropertyGroupDescriptionAttribute propertyGroupDescription); TreeViewModel TreeViewModel(LayerPropertiesViewModel layerPropertiesViewModel, BindableCollection layerPropertyGroups); EffectsViewModel EffectsViewModel(LayerPropertiesViewModel layerPropertiesViewModel); TimelineViewModel TimelineViewModel(LayerPropertiesViewModel layerPropertiesViewModel, BindableCollection layerPropertyGroups); - TreePropertyGroupViewModel TreePropertyGroupViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel); - TimelinePropertyGroupViewModel TimelinePropertyGroupViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel); } } \ No newline at end of file diff --git a/src/Artemis.UI/Ninject/UiModule.cs b/src/Artemis.UI/Ninject/UiModule.cs index 2903c5a48..3ae26f21d 100644 --- a/src/Artemis.UI/Ninject/UiModule.cs +++ b/src/Artemis.UI/Ninject/UiModule.cs @@ -53,7 +53,7 @@ namespace Artemis.UI.Ninject { x.FromThisAssembly() .SelectAllClasses() - .InheritedFrom() + .InheritedFrom() .BindAllBaseClasses(); }); diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Abstract/DisplayConditionViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Abstract/DisplayConditionViewModel.cs index 743fec551..65565fdb2 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Abstract/DisplayConditionViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Abstract/DisplayConditionViewModel.cs @@ -1,44 +1,23 @@ -using System; -using Artemis.Core; +using Artemis.Core; using Stylet; namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions.Abstract { - public abstract class DisplayConditionViewModel : PropertyChangedBase, IDisposable + public abstract class DisplayConditionViewModel : Conductor.Collection.AllActive { - protected DisplayConditionViewModel(DisplayConditionPart model, DisplayConditionViewModel parent) + protected DisplayConditionViewModel(DisplayConditionPart model) { Model = model; - Parent = parent; - Children = new BindableCollection(); } public DisplayConditionPart Model { get; } - public DisplayConditionViewModel Parent { get; set; } - public BindableCollection Children { get; } - - public void Dispose() - { - foreach (var child in Children) - child.Dispose(); - - Dispose(true); - GC.SuppressFinalize(this); - } public abstract void Update(); public virtual void Delete() { Model.Parent.RemoveChild(Model); - Parent.Update(); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - } + ((DisplayConditionViewModel) Parent).Update(); } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionGroupView.xaml b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionGroupView.xaml index b0ec8a679..f1a770d08 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionGroupView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionGroupView.xaml @@ -124,7 +124,7 @@ - + diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionGroupViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionGroupViewModel.cs index 1f1deae4d..a5caf78dd 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionGroupViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionGroupViewModel.cs @@ -1,7 +1,6 @@ using System; using System.Linq; using System.Threading.Tasks; -using System.Windows; using Artemis.Core; using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.ProfileEditor.DisplayConditions.Abstract; @@ -11,21 +10,24 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions { - public class DisplayConditionGroupViewModel : DisplayConditionViewModel, IViewAware + public class DisplayConditionGroupViewModel : DisplayConditionViewModel { private readonly IDisplayConditionsVmFactory _displayConditionsVmFactory; private readonly IProfileEditorService _profileEditorService; private bool _isInitialized; private bool _isRootGroup; - public DisplayConditionGroupViewModel(DisplayConditionGroup displayConditionGroup, DisplayConditionViewModel parent, bool isListGroup, - IProfileEditorService profileEditorService, IDisplayConditionsVmFactory displayConditionsVmFactory) : base(displayConditionGroup, parent) + public DisplayConditionGroupViewModel(DisplayConditionGroup displayConditionGroup, + bool isListGroup, + IProfileEditorService profileEditorService, + IDisplayConditionsVmFactory displayConditionsVmFactory) + : base(displayConditionGroup) { IsListGroup = isListGroup; _profileEditorService = profileEditorService; _displayConditionsVmFactory = displayConditionsVmFactory; - Children.CollectionChanged += (sender, args) => NotifyOfPropertyChange(nameof(DisplayBooleanOperator)); + Items.CollectionChanged += (sender, args) => NotifyOfPropertyChange(nameof(DisplayBooleanOperator)); Execute.PostToUIThread(async () => { @@ -50,16 +52,9 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions set => SetAndNotify(ref _isInitialized, value); } - public bool DisplayBooleanOperator => Children.Count > 1; + public bool DisplayBooleanOperator => Items.Count > 1; public string SelectedBooleanOperator => DisplayConditionGroup.BooleanOperator.Humanize(); - public void AttachView(UIElement view) - { - View = view; - } - - public UIElement View { get; set; } - public void SelectBooleanOperator(string type) { var enumValue = Enum.Parse(type); @@ -105,39 +100,36 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions NotifyOfPropertyChange(nameof(SelectedBooleanOperator)); // Remove VMs of effects no longer applied on the layer - var toRemove = Children.Where(c => !DisplayConditionGroup.Children.Contains(c.Model)).ToList(); + var toRemove = Items.Where(c => !DisplayConditionGroup.Children.Contains(c.Model)).ToList(); // Using RemoveRange breaks our lovely animations foreach (var displayConditionViewModel in toRemove) - { - Children.Remove(displayConditionViewModel); - displayConditionViewModel.Dispose(); - } + CloseItem(displayConditionViewModel); foreach (var childModel in Model.Children) { - if (Children.Any(c => c.Model == childModel)) + if (Items.Any(c => c.Model == childModel)) continue; switch (childModel) { case DisplayConditionGroup displayConditionGroup: - Children.Add(_displayConditionsVmFactory.DisplayConditionGroupViewModel(displayConditionGroup, this, IsListGroup)); + ActivateItem(_displayConditionsVmFactory.DisplayConditionGroupViewModel(displayConditionGroup, IsListGroup)); break; case DisplayConditionList displayConditionListPredicate: - Children.Add(_displayConditionsVmFactory.DisplayConditionListViewModel(displayConditionListPredicate, this)); + ActivateItem(_displayConditionsVmFactory.DisplayConditionListViewModel(displayConditionListPredicate)); break; case DisplayConditionPredicate displayConditionPredicate: if (!IsListGroup) - Children.Add(_displayConditionsVmFactory.DisplayConditionPredicateViewModel(displayConditionPredicate, this)); + ActivateItem(_displayConditionsVmFactory.DisplayConditionPredicateViewModel(displayConditionPredicate)); break; case DisplayConditionListPredicate displayConditionListPredicate: if (IsListGroup) - Children.Add(_displayConditionsVmFactory.DisplayConditionListPredicateViewModel(displayConditionListPredicate, this)); + ActivateItem(_displayConditionsVmFactory.DisplayConditionListPredicateViewModel(displayConditionListPredicate)); break; } } - foreach (var childViewModel in Children) + foreach (var childViewModel in Items) childViewModel.Update(); } } diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListPredicateViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListPredicateViewModel.cs index 08e7165ec..7d07d3c11 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListPredicateViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListPredicateViewModel.cs @@ -17,9 +17,9 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions { - public class DisplayConditionListPredicateViewModel : DisplayConditionViewModel, IHandle, IHandle + public class DisplayConditionListPredicateViewModel : DisplayConditionViewModel, IHandle, IHandle, IDisposable { - private readonly IDataModelService _dataModelService; + private readonly IConditionOperatorService _conditionOperatorService; private readonly IDataModelUIService _dataModelUIService; private readonly IEventAggregator _eventAggregator; private readonly IProfileEditorService _profileEditorService; @@ -39,16 +39,15 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions public DisplayConditionListPredicateViewModel( DisplayConditionListPredicate displayConditionListPredicate, - DisplayConditionViewModel parent, IProfileEditorService profileEditorService, IDataModelUIService dataModelUIService, - IDataModelService dataModelService, + IConditionOperatorService conditionOperatorService, ISettingsService settingsService, - IEventAggregator eventAggregator) : base(displayConditionListPredicate, parent) + IEventAggregator eventAggregator) : base(displayConditionListPredicate) { _profileEditorService = profileEditorService; _dataModelUIService = dataModelUIService; - _dataModelService = dataModelService; + _conditionOperatorService = conditionOperatorService; _eventAggregator = eventAggregator; _updateTimer = new Timer(500); _supportedInputTypes = new List(); @@ -206,7 +205,7 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions // Get the supported operators Operators.Clear(); - Operators.AddRange(_dataModelService.GetConditionOperatorsForType(leftSideType)); + Operators.AddRange(_conditionOperatorService.GetConditionOperatorsForType(leftSideType)); if (DisplayConditionListPredicate.Operator == null) DisplayConditionListPredicate.UpdateOperator(Operators.FirstOrDefault(o => o.SupportsType(leftSideType))); SelectedOperator = DisplayConditionListPredicate.Operator; @@ -277,12 +276,6 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions _eventAggregator.Subscribe(this); } - protected override void Dispose(bool disposing) - { - _updateTimer.Stop(); - _updateTimer.Elapsed -= OnUpdateTimerOnElapsed; - } - private void OnUpdateTimerOnElapsed(object sender, ElapsedEventArgs e) { if (LeftSideDataModelOpen) @@ -353,5 +346,11 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions SelectedOperator = displayConditionOperator; ApplyOperator(); } + + public void Dispose() + { + _updateTimer.Dispose(); + _updateTimer.Elapsed -= OnUpdateTimerOnElapsed; + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListView.xaml b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListView.xaml index 3ed7ca5d1..634955989 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListView.xaml @@ -105,7 +105,7 @@ - + diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListViewModel.cs index a2ab2fb6f..73393f6cb 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListViewModel.cs @@ -14,7 +14,7 @@ using Humanizer; namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions { - public class DisplayConditionListViewModel : DisplayConditionViewModel + public class DisplayConditionListViewModel : DisplayConditionViewModel, IDisposable { private readonly IDataModelUIService _dataModelUIService; private readonly IDisplayConditionsVmFactory _displayConditionsVmFactory; @@ -30,7 +30,7 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions IProfileEditorService profileEditorService, IDataModelUIService dataModelUIService, IDisplayConditionsVmFactory displayConditionsVmFactory, - ISettingsService settingsService) : base(displayConditionList, parent) + ISettingsService settingsService) : base(displayConditionList) { _profileEditorService = profileEditorService; _dataModelUIService = dataModelUIService; @@ -152,36 +152,27 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions TargetDataModel.ApplyTypeFilter(true, typeof(IList)); // Remove VMs of effects no longer applied on the layer - var toRemove = Children.Where(c => !DisplayConditionList.Children.Contains(c.Model)).ToList(); + var toRemove = Items.Where(c => !DisplayConditionList.Children.Contains(c.Model)).ToList(); // Using RemoveRange breaks our lovely animations foreach (var displayConditionViewModel in toRemove) - { - Children.Remove(displayConditionViewModel); - displayConditionViewModel.Dispose(); - } + CloseItem(displayConditionViewModel); foreach (var childModel in Model.Children) { - if (Children.Any(c => c.Model == childModel)) + if (Items.Any(c => c.Model == childModel)) continue; if (!(childModel is DisplayConditionGroup displayConditionGroup)) continue; - var viewModel = _displayConditionsVmFactory.DisplayConditionGroupViewModel(displayConditionGroup, this, true); + var viewModel = _displayConditionsVmFactory.DisplayConditionGroupViewModel(displayConditionGroup, true); viewModel.IsRootGroup = true; - Children.Add(viewModel); + ActivateItem(viewModel); } - foreach (var childViewModel in Children) + foreach (var childViewModel in Items) childViewModel.Update(); } - protected override void Dispose(bool disposing) - { - _updateTimer.Stop(); - _updateTimer.Elapsed -= OnUpdateTimerOnElapsed; - } - private void OnUpdateTimerOnElapsed(object sender, ElapsedEventArgs e) { if (TargetDataModelOpen) @@ -205,5 +196,11 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions SelectedListProperty = dataModelListViewModel; ApplyList(); } + + public void Dispose() + { + _updateTimer.Dispose(); + _updateTimer.Elapsed -= OnUpdateTimerOnElapsed; + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs index a7750ee91..f8b7833bd 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs @@ -16,9 +16,9 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions { - public class DisplayConditionPredicateViewModel : DisplayConditionViewModel, IHandle, IHandle + public class DisplayConditionPredicateViewModel : DisplayConditionViewModel, IHandle, IHandle, IDisposable { - private readonly IDataModelService _dataModelService; + private readonly IConditionOperatorService _conditionOperatorService; private readonly IDataModelUIService _dataModelUIService; private readonly IEventAggregator _eventAggregator; private readonly IProfileEditorService _profileEditorService; @@ -38,16 +38,15 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions public DisplayConditionPredicateViewModel( DisplayConditionPredicate displayConditionPredicate, - DisplayConditionViewModel parent, IProfileEditorService profileEditorService, IDataModelUIService dataModelUIService, - IDataModelService dataModelService, + IConditionOperatorService conditionOperatorService, ISettingsService settingsService, - IEventAggregator eventAggregator) : base(displayConditionPredicate, parent) + IEventAggregator eventAggregator) : base(displayConditionPredicate) { _profileEditorService = profileEditorService; _dataModelUIService = dataModelUIService; - _dataModelService = dataModelService; + _conditionOperatorService = conditionOperatorService; _eventAggregator = eventAggregator; _updateTimer = new Timer(500); _supportedInputTypes = new List(); @@ -208,7 +207,7 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions // Get the supported operators Operators.Clear(); - Operators.AddRange(_dataModelService.GetConditionOperatorsForType(leftSideType)); + Operators.AddRange(_conditionOperatorService.GetConditionOperatorsForType(leftSideType)); if (DisplayConditionPredicate.Operator == null) DisplayConditionPredicate.UpdateOperator(Operators.FirstOrDefault(o => o.SupportsType(leftSideType))); SelectedOperator = DisplayConditionPredicate.Operator; @@ -278,13 +277,7 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions ); _eventAggregator.Subscribe(this); } - - protected override void Dispose(bool disposing) - { - _updateTimer.Stop(); - _updateTimer.Elapsed -= OnUpdateTimerOnElapsed; - } - + private void OnUpdateTimerOnElapsed(object sender, ElapsedEventArgs e) { if (LeftSideDataModelOpen) @@ -335,5 +328,11 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions SelectedOperator = displayConditionOperator; ApplyOperator(); } + + public void Dispose() + { + _updateTimer.Dispose(); + _updateTimer.Elapsed -= OnUpdateTimerOnElapsed; + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml index 43ca3526b..68c8ef8f9 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml @@ -43,7 +43,7 @@ - + diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs index 2931a2907..e2d3a7a04 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs @@ -3,17 +3,17 @@ using Artemis.Core; using Artemis.UI.Ninject.Factories; using Artemis.UI.Shared; using Artemis.UI.Shared.Services; +using Stylet; namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions { - public class DisplayConditionsViewModel : ProfileEditorPanelViewModel + public class DisplayConditionsViewModel : Conductor, IProfileEditorPanelViewModel { private readonly IDisplayConditionsVmFactory _displayConditionsVmFactory; private readonly IProfileEditorService _profileEditorService; private bool _alwaysFinishTimeline; private bool _displayContinuously; private RenderProfileElement _renderProfileElement; - private DisplayConditionGroupViewModel _rootGroup; private int _transitionerIndex; public DisplayConditionsViewModel(IProfileEditorService profileEditorService, IDisplayConditionsVmFactory displayConditionsVmFactory) @@ -28,11 +28,6 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions set => SetAndNotify(ref _transitionerIndex, value); } - public DisplayConditionGroupViewModel RootGroup - { - get => _rootGroup; - set => SetAndNotify(ref _rootGroup, value); - } public RenderProfileElement RenderProfileElement { @@ -70,9 +65,6 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions protected override void OnDeactivate() { _profileEditorService.ProfileElementSelected -= ProfileEditorServiceOnProfileElementSelected; - - RootGroup?.Dispose(); - RootGroup = null; } private void ProfileEditorServiceOnProfileElementSelected(object sender, RenderProfileElementEventArgs e) @@ -87,8 +79,7 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions if (e.RenderProfileElement == null) { - RootGroup?.Dispose(); - RootGroup = null; + ActiveItem = null; return; } @@ -96,14 +87,13 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions if (e.RenderProfileElement.DisplayConditionGroup == null) e.RenderProfileElement.DisplayConditionGroup = new DisplayConditionGroup(null); - RootGroup?.Dispose(); - RootGroup = _displayConditionsVmFactory.DisplayConditionGroupViewModel(e.RenderProfileElement.DisplayConditionGroup, null, false); - RootGroup.IsRootGroup = true; - RootGroup.Update(); + ActiveItem = _displayConditionsVmFactory.DisplayConditionGroupViewModel(e.RenderProfileElement.DisplayConditionGroup, false); + ActiveItem.IsRootGroup = true; + ActiveItem.Update(); // Only show the intro to conditions once, and only if the layer has no conditions if (TransitionerIndex != 1) - TransitionerIndex = RootGroup.Children.Any() ? 1 : 0; + TransitionerIndex = ActiveItem.Items.Any() ? 1 : 0; } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorPanelViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/IProfileEditorPanelViewModel.cs similarity index 55% rename from src/Artemis.UI/Screens/ProfileEditor/ProfileEditorPanelViewModel.cs rename to src/Artemis.UI/Screens/ProfileEditor/IProfileEditorPanelViewModel.cs index e2829cefe..cf5601e85 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorPanelViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/IProfileEditorPanelViewModel.cs @@ -2,7 +2,7 @@ namespace Artemis.UI.Screens.ProfileEditor { - public class ProfileEditorPanelViewModel : Screen + public interface IProfileEditorPanelViewModel : IScreen { } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Abstract/LayerPropertyBaseViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Abstract/LayerPropertyBaseViewModel.cs deleted file mode 100644 index 96278e0c3..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Abstract/LayerPropertyBaseViewModel.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; -using Artemis.Core; -using Stylet; - -namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Abstract -{ - public abstract class LayerPropertyBaseViewModel : PropertyChangedBase, IDisposable - { - private BindableCollection _children; - private bool _isExpanded; - - protected LayerPropertyBaseViewModel() - { - Children = new BindableCollection(); - } - - public abstract bool IsVisible { get; } - - public virtual bool IsExpanded - { - get => _isExpanded; - set => SetAndNotify(ref _isExpanded, value); - } - - public BindableCollection Children - { - get => _children; - set => SetAndNotify(ref _children, value); - } - - public abstract void Dispose(); - - public abstract List GetKeyframes(bool expandedOnly); - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerEffects/EffectsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerEffects/EffectsViewModel.cs index 3423e21d3..199318a63 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerEffects/EffectsViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerEffects/EffectsViewModel.cs @@ -9,32 +9,22 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.LayerEffects { - public class EffectsViewModel : PropertyChangedBase + public class EffectsViewModel : Conductor.Collection.AllActive { private readonly IPluginService _pluginService; private readonly IProfileEditorService _profileEditorService; - private readonly IRenderElementService _renderElementService; - private BindableCollection _layerEffectDescriptors; private LayerEffectDescriptor _selectedLayerEffectDescriptor; - public EffectsViewModel(LayerPropertiesViewModel layerPropertiesViewModel, IPluginService pluginService, IRenderElementService renderElementService, IProfileEditorService profileEditorService) + public EffectsViewModel(LayerPropertiesViewModel layerPropertiesViewModel, IPluginService pluginService, IProfileEditorService profileEditorService) { _pluginService = pluginService; - _renderElementService = renderElementService; _profileEditorService = profileEditorService; LayerPropertiesViewModel = layerPropertiesViewModel; - LayerEffectDescriptors = new BindableCollection(); PropertyChanged += HandleSelectedLayerEffectChanged; } public LayerPropertiesViewModel LayerPropertiesViewModel { get; } - public bool HasLayerEffectDescriptors => LayerEffectDescriptors.Any(); - - public BindableCollection LayerEffectDescriptors - { - get => _layerEffectDescriptors; - set => SetAndNotify(ref _layerEffectDescriptors, value); - } + public bool HasLayerEffectDescriptors => Items.Any(); public LayerEffectDescriptor SelectedLayerEffectDescriptor { @@ -46,15 +36,15 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.LayerEffects { var layerBrushProviders = _pluginService.GetPluginsOfType(); var descriptors = layerBrushProviders.SelectMany(l => l.LayerEffectDescriptors).ToList(); - LayerEffectDescriptors.AddRange(descriptors.Except(LayerEffectDescriptors)); - LayerEffectDescriptors.RemoveRange(LayerEffectDescriptors.Except(descriptors)); + Items.AddRange(descriptors.Except(Items)); + Items.RemoveRange(Items.Except(descriptors)); // Sort by display name var index = 0; - foreach (var layerEffectDescriptor in LayerEffectDescriptors.OrderBy(d => d.DisplayName).ToList()) + foreach (var layerEffectDescriptor in Items.OrderBy(d => d.DisplayName).ToList()) { - if (LayerEffectDescriptors.IndexOf(layerEffectDescriptor) != index) - LayerEffectDescriptors.Move(LayerEffectDescriptors.IndexOf(layerEffectDescriptor), index); + if (Items.IndexOf(layerEffectDescriptor) != index) + ((BindableCollection) Items).Move(Items.IndexOf(layerEffectDescriptor), index); index++; } @@ -78,7 +68,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.LayerEffects Execute.PostToUIThread(async () => { await Task.Delay(500); - _renderElementService.AddLayerEffect(renderElement, SelectedLayerEffectDescriptor); + renderElement.AddLayerEffect(SelectedLayerEffectDescriptor); _profileEditorService.UpdateSelectedProfileElement(); }); } diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs index 4aebe4602..68c462ff1 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs @@ -16,11 +16,10 @@ using Artemis.UI.Shared; using Artemis.UI.Shared.Services; using GongSolutions.Wpf.DragDrop; using Stylet; -using static Artemis.UI.Screens.ProfileEditor.LayerProperties.LayerPropertyGroupViewModel.ViewModelType; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties { - public class LayerPropertiesViewModel : ProfileEditorPanelViewModel, IDropTarget + public class LayerPropertiesViewModel : Conductor.Collection.AllActive, IProfileEditorPanelViewModel, IDropTarget { private readonly ILayerPropertyVmFactory _layerPropertyVmFactory; private readonly IDataBindingsVmFactory _dataBindingsVmFactory; diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs index 15cbe89f3..3f87b3479 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs @@ -1,58 +1,31 @@ using System; -using System.Collections.Generic; -using System.Linq; using Artemis.Core; using Artemis.UI.Ninject.Factories; -using Artemis.UI.Screens.ProfileEditor.LayerProperties.Abstract; -using Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline; using Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree; -using Artemis.UI.Shared.Services; -using Humanizer; -using Ninject; -using Ninject.Parameters; +using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties { - public class LayerPropertyGroupViewModel : LayerPropertyBaseViewModel + public class LayerPropertyGroupViewModel : PropertyChangedBase, IDisposable { private readonly ILayerPropertyVmFactory _layerPropertyVmFactory; - private ViewModelType _groupType; - private TimelinePropertyGroupViewModel _timelinePropertyGroupViewModel; - private TreePropertyGroupViewModel _treePropertyGroupViewModel; - public LayerPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, PropertyGroupDescriptionAttribute propertyGroupDescription, - IProfileEditorService profileEditorService, ILayerPropertyVmFactory layerPropertyVmFactory) + public LayerPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory) { _layerPropertyVmFactory = layerPropertyVmFactory; - ProfileEditorService = profileEditorService; LayerPropertyGroup = layerPropertyGroup; - PropertyGroupDescription = propertyGroupDescription; - - TreePropertyGroupViewModel = _layerPropertyVmFactory.TreePropertyGroupViewModel(this); - TimelinePropertyGroupViewModel = _layerPropertyVmFactory.TimelinePropertyGroupViewModel(this); - - // Generate a fallback name if the description does not contain one - if (PropertyGroupDescription.Name == null) - { - var propertyInfo = LayerPropertyGroup.Parent?.GetType().GetProperties().FirstOrDefault(p => ReferenceEquals(p.GetValue(LayerPropertyGroup.Parent), LayerPropertyGroup)); - if (propertyInfo != null) - PropertyGroupDescription.Name = propertyInfo.Name.Humanize(); - else - PropertyGroupDescription.Name = "Unknown group"; - } - - LayerPropertyGroup.VisibilityChanged += LayerPropertyGroupOnVisibilityChanged; + LayerPropertyGroupTreeViewModel = layerPropertyVmFactory.LayerPropertyGroupTreeViewModel(this); PopulateChildren(); - DetermineType(); } - public override bool IsVisible => !LayerPropertyGroup.IsHidden; - public IProfileEditorService ProfileEditorService { get; } public LayerPropertyGroup LayerPropertyGroup { get; } - public PropertyGroupDescriptionAttribute PropertyGroupDescription { get; } + public LayerPropertyGroupTreeViewModel LayerPropertyGroupTreeViewModel { get; } + public BindableCollection Children { get; set; } - public override bool IsExpanded + public bool IsVisible => !LayerPropertyGroup.IsHidden; + + public bool IsExpanded { get => LayerPropertyGroup.ProfileElement.IsPropertyGroupExpanded(LayerPropertyGroup); set @@ -62,81 +35,10 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties } } - public ViewModelType GroupType - { - get => _groupType; - set => SetAndNotify(ref _groupType, value); - } - - public TreePropertyGroupViewModel TreePropertyGroupViewModel - { - get => _treePropertyGroupViewModel; - set => SetAndNotify(ref _treePropertyGroupViewModel, value); - } - - public TimelinePropertyGroupViewModel TimelinePropertyGroupViewModel - { - get => _timelinePropertyGroupViewModel; - set => SetAndNotify(ref _timelinePropertyGroupViewModel, value); - } - - public override List GetKeyframes(bool expandedOnly) - { - var result = new List(); - if (expandedOnly && !IsExpanded || LayerPropertyGroup.IsHidden) - return result; - - foreach (var layerPropertyBaseViewModel in Children) - result.AddRange(layerPropertyBaseViewModel.GetKeyframes(expandedOnly)); - - return result; - } - - public override void Dispose() - { - foreach (var layerPropertyBaseViewModel in Children) - layerPropertyBaseViewModel.Dispose(); - - LayerPropertyGroup.VisibilityChanged -= LayerPropertyGroupOnVisibilityChanged; - TimelinePropertyGroupViewModel.Dispose(); - } - - public List GetAllChildren() - { - var result = new List(); - foreach (var layerPropertyBaseViewModel in Children) - { - result.Add(layerPropertyBaseViewModel); - if (layerPropertyBaseViewModel is LayerPropertyGroupViewModel layerPropertyGroupViewModel) - result.AddRange(layerPropertyGroupViewModel.GetAllChildren()); - } - - return result; - } - - public void UpdateOrder(int order) - { - LayerPropertyGroup.LayerEffect.Order = order; - NotifyOfPropertyChange(nameof(IsExpanded)); - } - - private void DetermineType() - { - if (LayerPropertyGroup is LayerGeneralProperties) - GroupType = ViewModelType.General; - else if (LayerPropertyGroup is LayerTransformProperties) - GroupType = ViewModelType.Transform; - else if (LayerPropertyGroup.Parent == null && LayerPropertyGroup.LayerBrush != null) - GroupType = ViewModelType.LayerBrushRoot; - else if (LayerPropertyGroup.Parent == null && LayerPropertyGroup.LayerEffect != null) - GroupType = ViewModelType.LayerEffectRoot; - else - GroupType = ViewModelType.None; - } - private void PopulateChildren() { // Get all properties and property groups and create VMs for them + // The group has methods for getting this without reflection but then we lose the order of the properties as they are defined on the group foreach (var propertyInfo in LayerPropertyGroup.GetType().GetProperties()) { var propertyAttribute = (PropertyDescriptionAttribute) Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyDescriptionAttribute)); @@ -144,50 +46,28 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties var value = propertyInfo.GetValue(LayerPropertyGroup); // Create VMs for properties on the group - if (propertyAttribute != null && value is BaseLayerProperty baseLayerProperty) + if (propertyAttribute != null && value is ILayerProperty layerProperty) { - var viewModel = CreateLayerPropertyViewModel(baseLayerProperty, propertyAttribute); - if (viewModel != null) - Children.Add(viewModel); + var layerPropertyViewModel = _layerPropertyVmFactory.LayerPropertyViewModel(layerProperty); + // After creation ensure a supported input VM was found, if not, discard the VM + if (!layerPropertyViewModel.LayerPropertyTreeViewModel.HasPropertyInputViewModel) + layerPropertyViewModel.Dispose(); + else + Children.Add(layerPropertyViewModel); } // Create VMs for child groups on this group, resulting in a nested structure else if (groupAttribute != null && value is LayerPropertyGroup layerPropertyGroup) - Children.Add(_layerPropertyVmFactory.LayerPropertyGroupViewModel(layerPropertyGroup, groupAttribute)); + Children.Add(_layerPropertyVmFactory.LayerPropertyGroupViewModel(layerPropertyGroup)); } } - private LayerPropertyBaseViewModel CreateLayerPropertyViewModel(BaseLayerProperty baseLayerProperty, PropertyDescriptionAttribute propertyDescription) + public void Dispose() { - // Go through the pain of instantiating a generic type VM now via reflection to make things a lot simpler down the line - var genericType = baseLayerProperty.GetType().Name == typeof(LayerProperty<>).Name - ? baseLayerProperty.GetType().GetGenericArguments()[0] - : baseLayerProperty.GetType().BaseType.GetGenericArguments()[0]; - - // Only create entries for types supported by a tree input VM - if (!genericType.IsEnum && ProfileEditorService.RegisteredPropertyEditors.All(r => r.SupportedType != genericType)) - return null; - var genericViewModel = typeof(LayerPropertyViewModel<>).MakeGenericType(genericType); - var parameters = new IParameter[] + foreach (var child in Children) { - new ConstructorArgument("layerProperty", baseLayerProperty), - new ConstructorArgument("propertyDescription", propertyDescription) - }; - - return (LayerPropertyBaseViewModel) ProfileEditorService.Kernel.Get(genericViewModel, parameters); - } - - private void LayerPropertyGroupOnVisibilityChanged(object sender, EventArgs e) - { - NotifyOfPropertyChange(nameof(IsVisible)); - } - - public enum ViewModelType - { - General, - Transform, - LayerBrushRoot, - LayerEffectRoot, - None + if (child is IDisposable disposableChild) + disposableChild.Dispose(); + } } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs index 542c0730b..f436b7f0d 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs @@ -1,141 +1,37 @@ using System; -using System.Collections.Generic; -using System.Linq; using Artemis.Core; -using Artemis.UI.Exceptions; -using Artemis.UI.PropertyInput; -using Artemis.UI.Screens.ProfileEditor.LayerProperties.Abstract; -using Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline; using Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree; -using Artemis.UI.Shared; -using Artemis.UI.Shared.Services; -using Humanizer; using Ninject; using Ninject.Parameters; +using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties { - public class LayerPropertyViewModel : LayerPropertyViewModel + public class LayerPropertyViewModel : PropertyChangedBase, IDisposable { - private TimelinePropertyViewModel _timelinePropertyViewModel; - private TreePropertyViewModel _treePropertyViewModel; - - public LayerPropertyViewModel(IProfileEditorService profileEditorService, LayerProperty layerProperty) : base(profileEditorService, layerProperty) + public LayerPropertyViewModel(ILayerProperty layerProperty, IKernel kernel) { LayerProperty = layerProperty; - TreePropertyViewModel = CreateTreePropertyViewModel(); - TimelinePropertyViewModel = new TimelinePropertyViewModel(this, profileEditorService); + var parameter = new ConstructorArgument("layerProperty", LayerProperty); + var treeViewModelType = typeof(LayerPropertyTreeViewModel<>).MakeGenericType(layerProperty.GetType().GetGenericArguments()); + var timelineViewModelType = typeof(LayerPropertyTimelineViewModel<>).MakeGenericType(layerProperty.GetType().GetGenericArguments()); - TreePropertyBaseViewModel = TreePropertyViewModel; - TimelinePropertyBaseViewModel = TimelinePropertyViewModel; - - // Generate a fallback name if the description does not contain one - if (LayerProperty.PropertyDescription.Name == null) - { - var propertyInfo = LayerProperty.Parent?.GetType().GetProperties().FirstOrDefault(p => ReferenceEquals(p.GetValue(LayerProperty.Parent), LayerProperty)); - if (propertyInfo != null) - LayerProperty.PropertyDescription.Name = propertyInfo.Name.Humanize(); - else - LayerProperty.PropertyDescription.Name = $"Unknown {typeof(T).Name} property"; - } - - LayerProperty.VisibilityChanged += LayerPropertyOnVisibilityChanged; + LayerPropertyTreeViewModel = (ILayerPropertyTreeViewModel) kernel.Get(treeViewModelType, parameter); + LayerPropertyTimelineViewModel = (ILayerPropertyTimelineViewModel) kernel.Get(timelineViewModelType, parameter); } - public override bool IsVisible => !LayerProperty.IsHidden; + public ILayerProperty LayerProperty { get; } + public ILayerPropertyTreeViewModel LayerPropertyTreeViewModel { get; } + public ILayerPropertyTimelineViewModel LayerPropertyTimelineViewModel { get; } - public LayerProperty LayerProperty { get; } + public bool IsVisible { get; set; } + public bool IsExpanded { get; set; } - public TreePropertyViewModel TreePropertyViewModel + public void Dispose() { - get => _treePropertyViewModel; - set => SetAndNotify(ref _treePropertyViewModel, value); - } - - public TimelinePropertyViewModel TimelinePropertyViewModel - { - get => _timelinePropertyViewModel; - set => SetAndNotify(ref _timelinePropertyViewModel, value); - } - - public override List GetKeyframes(bool expandedOnly) - { - if (LayerProperty.KeyframesEnabled && !LayerProperty.IsHidden) - return LayerProperty.BaseKeyframes.ToList(); - return new List(); - } - - public override void Dispose() - { - TreePropertyViewModel.Dispose(); - TimelinePropertyViewModel.Dispose(); - - LayerProperty.VisibilityChanged -= LayerPropertyOnVisibilityChanged; - } - - public void SetCurrentValue(T value, bool saveChanges) - { - LayerProperty.SetCurrentValue(value, ProfileEditorService.CurrentTime); - if (saveChanges) - ProfileEditorService.UpdateSelectedProfileElement(); - else - ProfileEditorService.UpdateProfilePreview(); - } - - private TreePropertyViewModel CreateTreePropertyViewModel() - { - // Make sure there is a supported property editor VM, unless the type is an enum, then we'll use the EnumPropertyInputViewModel - Type vmType = null; - if (typeof(T).IsEnum) - vmType = typeof(EnumPropertyInputViewModel<>).MakeGenericType(typeof(T)); - else - { - var registration = ProfileEditorService.RegisteredPropertyEditors.FirstOrDefault(r => r.SupportedType == typeof(T)); - if (registration != null) - vmType = registration.ViewModelType; - } - - if (vmType == null) - throw new ArtemisUIException($"Cannot create a tree property view model for type {typeof(T)}, found no matching property editor"); - - var parameters = new IParameter[] - { - new ConstructorArgument("layerProperty", LayerProperty) - }; - return new TreePropertyViewModel(this, (PropertyInputViewModel) ProfileEditorService.Kernel.Get(vmType, parameters), ProfileEditorService); - } - - private void LayerPropertyOnVisibilityChanged(object sender, EventArgs e) - { - NotifyOfPropertyChange(nameof(IsVisible)); - } - } - - public abstract class LayerPropertyViewModel : LayerPropertyBaseViewModel - { - private TimelinePropertyViewModel _timelinePropertyBaseViewModel; - private TreePropertyViewModel _treePropertyBaseViewModel; - - protected LayerPropertyViewModel(IProfileEditorService profileEditorService, BaseLayerProperty baseLayerProperty) - { - ProfileEditorService = profileEditorService; - BaseLayerProperty = baseLayerProperty; - } - - public IProfileEditorService ProfileEditorService { get; } - public BaseLayerProperty BaseLayerProperty { get; } - - public TreePropertyViewModel TreePropertyBaseViewModel - { - get => _treePropertyBaseViewModel; - set => SetAndNotify(ref _treePropertyBaseViewModel, value); - } - - public TimelinePropertyViewModel TimelinePropertyBaseViewModel - { - get => _timelinePropertyBaseViewModel; - set => SetAndNotify(ref _timelinePropertyBaseViewModel, value); + LayerPropertyTreeViewModel?.Dispose(); + LayerPropertyTimelineViewModel?.Dispose(); } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Rails/LayerPropertyTimelineViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Rails/LayerPropertyTimelineViewModel.cs new file mode 100644 index 000000000..370dc0904 --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Rails/LayerPropertyTimelineViewModel.cs @@ -0,0 +1,26 @@ +using System; +using Artemis.Core; +using Stylet; + +namespace Artemis.UI.Screens.ProfileEditor.LayerProperties +{ + public class LayerPropertyTimelineViewModel : Screen, ILayerPropertyTimelineViewModel + { + public LayerProperty LayerProperty { get; } + public LayerPropertyViewModel LayerPropertyViewModel { get; } + + public LayerPropertyTimelineViewModel(LayerProperty layerProperty, LayerPropertyViewModel layerPropertyViewModel) + { + LayerProperty = layerProperty; + LayerPropertyViewModel = layerPropertyViewModel; + } + + public void Dispose() + { + } + } + + public interface ILayerPropertyTimelineViewModel : IScreen, IDisposable + { + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Rails/TimelineGroupViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Rails/TimelineGroupViewModel.cs new file mode 100644 index 000000000..fb39a056f --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Rails/TimelineGroupViewModel.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Artemis.Core; + +namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline.Rails +{ + public class TimelineGroupViewModel + { + public LayerPropertyGroup LayerPropertyGroup { get; } + + public TimelineGroupViewModel(LayerPropertyGroup layerPropertyGroup) + { + LayerPropertyGroup = layerPropertyGroup; + } + } +} diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreePropertyGroupView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/LayerPropertyGroupTreeView.xaml similarity index 71% rename from src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreePropertyGroupView.xaml rename to src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/LayerPropertyGroupTreeView.xaml index 406333f4d..a33638fbd 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreePropertyGroupView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/LayerPropertyGroupTreeView.xaml @@ -9,7 +9,7 @@ xmlns:converters="clr-namespace:Artemis.UI.Converters" xmlns:dd="urn:gong-wpf-dragdrop" mc:Ignorable="d" - d:DataContext="{d:DesignInstance local:TreePropertyGroupViewModel}" + d:DataContext="{d:DesignInstance local:LayerPropertyGroupTreeViewModel}" d:DesignHeight="450" d:DesignWidth="800"> @@ -18,8 +18,8 @@ @@ -27,7 +27,7 @@ - General + General @@ -57,14 +57,14 @@ - Transform + Transform @@ -79,23 +79,23 @@ Brush -  @@ -125,7 +125,7 @@ - - + + - - + + diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeViewModel.cs index 15ba072bb..5c84fa57f 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeViewModel.cs @@ -5,11 +5,13 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree { - public class TreeViewModel : PropertyChangedBase + public class TreeViewModel : Screen { public TreeViewModel(LayerPropertiesViewModel layerPropertiesViewModel, BindableCollection layerPropertyGroups) { LayerPropertiesViewModel = layerPropertiesViewModel; + + // Not using the Items collection because the list should persist even after this VM gets closed LayerPropertyGroups = layerPropertyGroups; } diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs index 52d80ed29..4e459d2b8 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs @@ -17,7 +17,7 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor { - public class ProfileEditorViewModel : Conductor.Collection.AllActive + public class ProfileEditorViewModel : Conductor.Collection.AllActive { private readonly IModuleService _moduleService; private readonly IProfileEditorService _profileEditorService; @@ -36,7 +36,7 @@ namespace Artemis.UI.Screens.ProfileEditor private PluginSetting _sidePanelsWidth; public ProfileEditorViewModel(ProfileModule module, - ICollection viewModels, + ICollection viewModels, IProfileEditorService profileEditorService, IProfileService profileService, IDialogService dialogService, diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeView.xaml b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeView.xaml index c4cbdf640..32eb00d36 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeView.xaml @@ -27,7 +27,7 @@ - + - + diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs index 1593bfecc..6eee624df 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs @@ -11,24 +11,17 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.ProfileTree { - public class ProfileTreeViewModel : ProfileEditorPanelViewModel, IDropTarget + public class ProfileTreeViewModel : Conductor, IProfileEditorPanelViewModel, IDropTarget { - private readonly IFolderVmFactory _folderVmFactory; + private readonly IProfileTreeVmFactory _profileTreeVmFactory; private readonly IProfileEditorService _profileEditorService; - private FolderViewModel _rootFolder; private TreeItemViewModel _selectedTreeItem; private bool _updatingTree; - public ProfileTreeViewModel(IProfileEditorService profileEditorService, IFolderVmFactory folderVmFactory) + public ProfileTreeViewModel(IProfileEditorService profileEditorService, IProfileTreeVmFactory profileTreeVmFactory) { _profileEditorService = profileEditorService; - _folderVmFactory = folderVmFactory; - } - - public FolderViewModel RootFolder - { - get => _rootFolder; - set => SetAndNotify(ref _rootFolder, value); + _profileTreeVmFactory = profileTreeVmFactory; } public TreeItemViewModel SelectedTreeItem @@ -73,7 +66,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree switch (dragDropType) { case DragDropType.Add: - source.Parent.RemoveExistingElement(source); + ((TreeItemViewModel) source.Parent).RemoveExistingElement(source); target.AddExistingElement(source); break; case DragDropType.InsertBefore: @@ -92,27 +85,25 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree // ReSharper disable once UnusedMember.Global - Called from view public void AddFolder() { - RootFolder?.AddFolder(); + ActiveItem?.AddFolder(); } // ReSharper disable once UnusedMember.Global - Called from view public void AddLayer() { - RootFolder?.AddLayer(); + ActiveItem?.AddLayer(); } protected override void OnInitialActivate() { Subscribe(); CreateRootFolderViewModel(); + base.OnInitialActivate(); } protected override void OnClose() { Unsubscribe(); - - RootFolder?.Dispose(); - RootFolder = null; base.OnClose(); } @@ -122,12 +113,11 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree var firstChild = _profileEditorService.SelectedProfile?.Children?.FirstOrDefault(); if (!(firstChild is Folder folder)) { - RootFolder = null; + ActivateItem(null); return; } - RootFolder?.Dispose(); - RootFolder = _folderVmFactory.Create(folder); + ActivateItem(_profileTreeVmFactory.FolderViewModel(folder)); _updatingTree = false; // Auto-select the first layer @@ -150,7 +140,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree { if (parent == source) return DragDropType.None; - parent = parent.Parent; + parent = (TreeItemViewModel) parent.Parent; } switch (dropInfo.InsertPosition) @@ -186,20 +176,20 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree if (e.RenderProfileElement == SelectedTreeItem?.ProfileElement) return; - if (RootFolder == null) + if (ActiveItem == null) { CreateRootFolderViewModel(); return; } _updatingTree = true; - RootFolder.UpdateProfileElements(); + ActiveItem.UpdateProfileElements(); _updatingTree = false; if (e.RenderProfileElement == null) SelectedTreeItem = null; else { - var match = RootFolder.GetAllChildren().FirstOrDefault(vm => vm.ProfileElement == e.RenderProfileElement); + var match = ActiveItem.GetAllChildren().FirstOrDefault(vm => vm.ProfileElement == e.RenderProfileElement); if (match != null) SelectedTreeItem = match; } diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/FolderViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/FolderViewModel.cs index 16d594f11..cfc085612 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/FolderViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/FolderViewModel.cs @@ -1,5 +1,4 @@ using Artemis.Core; -using Artemis.Core.Services; using Artemis.UI.Ninject.Factories; using Artemis.UI.Shared.Services; @@ -11,36 +10,11 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem public FolderViewModel(ProfileElement folder, IProfileEditorService profileEditorService, IDialogService dialogService, - IRenderElementService renderElementService, - IFolderVmFactory folderVmFactory, - ILayerVmFactory layerVmFactory) : - base(null, folder, profileEditorService, dialogService, renderElementService, folderVmFactory, layerVmFactory) - { - } - - public FolderViewModel(TreeItemViewModel parent, - ProfileElement folder, - IProfileEditorService profileEditorService, - IDialogService dialogService, - IRenderElementService renderElementService, - IFolderVmFactory folderVmFactory, - ILayerVmFactory layerVmFactory) : - base(parent, folder, profileEditorService, dialogService, renderElementService, folderVmFactory, layerVmFactory) + IProfileTreeVmFactory profileTreeVmFactory) : + base(folder, profileEditorService, dialogService, profileTreeVmFactory) { } public override bool SupportsChildren => true; - - protected override void Dispose(bool disposing) - { - if (disposing) - { - foreach (var treeItemViewModel in Children) - treeItemViewModel.Dispose(); - Children.Clear(); - } - - base.Dispose(disposing); - } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerViewModel.cs index c539a5a55..ce0e43ebc 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerViewModel.cs @@ -1,5 +1,4 @@ using Artemis.Core; -using Artemis.Core.Services; using Artemis.UI.Ninject.Factories; using Artemis.UI.Shared.Services; @@ -7,14 +6,11 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem { public class LayerViewModel : TreeItemViewModel { - public LayerViewModel(TreeItemViewModel parent, - ProfileElement folder, + public LayerViewModel(ProfileElement layer, IProfileEditorService profileEditorService, IDialogService dialogService, - IRenderElementService renderElementService, - IFolderVmFactory folderVmFactory, - ILayerVmFactory layerVmFactory) : - base(parent, folder, profileEditorService, dialogService, renderElementService, folderVmFactory, layerVmFactory) + IProfileTreeVmFactory profileTreeVmFactory) : + base(layer, profileEditorService, dialogService, profileTreeVmFactory) { } diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs index a8d7f006b..cc923620d 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Artemis.Core; -using Artemis.Core.Services; using Artemis.UI.Exceptions; using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.ProfileEditor.Dialogs; @@ -12,66 +11,45 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem { - public abstract class TreeItemViewModel : PropertyChangedBase, IDisposable + public abstract class TreeItemViewModel : Conductor.Collection.AllActive, IDisposable { private readonly IDialogService _dialogService; - private readonly IFolderVmFactory _folderVmFactory; - private readonly ILayerVmFactory _layerVmFactory; private readonly IProfileEditorService _profileEditorService; - private readonly IRenderElementService _renderElementService; - private TreeItemViewModel _parent; + private readonly IProfileTreeVmFactory _profileTreeVmFactory; private ProfileElement _profileElement; - protected TreeItemViewModel(TreeItemViewModel parent, - ProfileElement profileElement, + protected TreeItemViewModel(ProfileElement profileElement, IProfileEditorService profileEditorService, IDialogService dialogService, - IRenderElementService renderElementService, - IFolderVmFactory folderVmFactory, - ILayerVmFactory layerVmFactory) + IProfileTreeVmFactory profileTreeVmFactory) { _profileEditorService = profileEditorService; _dialogService = dialogService; - _renderElementService = renderElementService; - _folderVmFactory = folderVmFactory; - _layerVmFactory = layerVmFactory; + _profileTreeVmFactory = profileTreeVmFactory; - Parent = parent; ProfileElement = profileElement; - Children = new BindableCollection(); - Subscribe(); UpdateProfileElements(); } - public TreeItemViewModel Parent - { - get => _parent; - set => SetAndNotify(ref _parent, value); - } - public ProfileElement ProfileElement { get => _profileElement; set => SetAndNotify(ref _profileElement, value); } - public BindableCollection Children { get; } - public abstract bool SupportsChildren { get; } public void Dispose() { Unsubscribe(); - Dispose(true); - GC.SuppressFinalize(this); } public List GetAllChildren() { var children = new List(); - foreach (var childFolder in Children) + foreach (var childFolder in Items) { // Add all children in this element children.Add(childFolder); @@ -84,34 +62,38 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem public void SetElementInFront(TreeItemViewModel source) { + var sourceParent = (TreeItemViewModel) source.Parent; + var parent = (TreeItemViewModel) Parent; if (source.Parent != Parent) { - source.Parent.RemoveExistingElement(source); - Parent.AddExistingElement(source); + sourceParent.RemoveExistingElement(source); + parent.AddExistingElement(source); } - Parent.Unsubscribe(); - Parent.ProfileElement.RemoveChild(source.ProfileElement); - Parent.ProfileElement.AddChild(source.ProfileElement, ProfileElement.Order); - Parent.Subscribe(); + parent.Unsubscribe(); + parent.ProfileElement.RemoveChild(source.ProfileElement); + parent.ProfileElement.AddChild(source.ProfileElement, ProfileElement.Order); + parent.Subscribe(); - Parent.UpdateProfileElements(); + parent.UpdateProfileElements(); } public void SetElementBehind(TreeItemViewModel source) { + var sourceParent = (TreeItemViewModel) source.Parent; + var parent = (TreeItemViewModel) Parent; if (source.Parent != Parent) { - source.Parent.RemoveExistingElement(source); - Parent.AddExistingElement(source); + sourceParent.RemoveExistingElement(source); + parent.AddExistingElement(source); } - Parent.Unsubscribe(); - Parent.ProfileElement.RemoveChild(source.ProfileElement); - Parent.ProfileElement.AddChild(source.ProfileElement, ProfileElement.Order + 1); - Parent.Subscribe(); + parent.Unsubscribe(); + parent.ProfileElement.RemoveChild(source.ProfileElement); + parent.ProfileElement.AddChild(source.ProfileElement, ProfileElement.Order + 1); + parent.Subscribe(); - Parent.UpdateProfileElements(); + parent.UpdateProfileElements(); } public void RemoveExistingElement(TreeItemViewModel treeItem) @@ -138,7 +120,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem if (!SupportsChildren) throw new ArtemisUIException("Cannot add a folder to a profile element of type " + ProfileElement.GetType().Name); - ProfileElement.AddChild(new Folder(ProfileElement.Profile, ProfileElement, "New folder")); + var _ = new Folder(ProfileElement, "New folder"); _profileEditorService.UpdateSelectedProfile(); } @@ -147,7 +129,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem if (!SupportsChildren) throw new ArtemisUIException("Cannot add a layer to a profile element of type " + ProfileElement.GetType().Name); - _renderElementService.CreateLayer(ProfileElement.Profile, ProfileElement, "New layer"); + var _ = new Layer(ProfileElement, "New layer"); _profileEditorService.UpdateSelectedProfile(); } @@ -180,7 +162,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem return; // Farewell, cruel world - var parent = Parent; + var parent = (TreeItemViewModel) Parent; ProfileElement.Parent?.RemoveChild(ProfileElement); parent.RemoveExistingElement(this); @@ -190,17 +172,17 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem public void UpdateProfileElements() { // Remove VMs that are no longer a child - var toRemove = Children.Where(c => c.ProfileElement.Parent != ProfileElement).ToList(); + var toRemove = Items.Where(c => c.ProfileElement.Parent != ProfileElement).ToList(); foreach (var treeItemViewModel in toRemove) - Children.Remove(treeItemViewModel); + DeactivateItem(treeItemViewModel); // Order the children - var vmsList = Children.OrderBy(v => v.ProfileElement.Order).ToList(); + var vmsList = Items.OrderBy(v => v.ProfileElement.Order).ToList(); for (var index = 0; index < vmsList.Count; index++) { var profileElementViewModel = vmsList[index]; - if (Children.IndexOf(profileElementViewModel) != index) - Children.Move(Children.IndexOf(profileElementViewModel), index); + if (Items.IndexOf(profileElementViewModel) != index) + ((BindableCollection) Items).Move(Items.IndexOf(profileElementViewModel), index); } // Ensure every child element has an up-to-date VM @@ -212,13 +194,13 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem { if (profileElement is Folder folder) { - if (Children.FirstOrDefault(p => p is FolderViewModel vm && vm.ProfileElement == folder) == null) - newChildren.Add(_folderVmFactory.Create((FolderViewModel) this, folder)); + if (Items.FirstOrDefault(p => p is FolderViewModel vm && vm.ProfileElement == folder) == null) + newChildren.Add(_profileTreeVmFactory.FolderViewModel(folder)); } else if (profileElement is Layer layer) { - if (Children.FirstOrDefault(p => p is LayerViewModel vm && vm.ProfileElement == layer) == null) - newChildren.Add(_layerVmFactory.Create((FolderViewModel) this, layer)); + if (Items.FirstOrDefault(p => p is LayerViewModel vm && vm.ProfileElement == layer) == null) + newChildren.Add(_profileTreeVmFactory.LayerViewModel(layer)); } } @@ -229,7 +211,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem foreach (var treeItemViewModel in newChildren) { treeItemViewModel.UpdateProfileElements(); - Children.Add(treeItemViewModel); + ActivateItem(treeItemViewModel); } } @@ -238,13 +220,6 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem _profileEditorService.UpdateSelectedProfile(); } - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - } - } - private void Subscribe() { ProfileElement.ChildAdded += ProfileElementOnChildAdded; diff --git a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileDeviceView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileDeviceView.xaml deleted file mode 100644 index 82a471bc7..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileDeviceView.xaml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileDeviceViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileDeviceViewModel.cs deleted file mode 100644 index 828600820..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileDeviceViewModel.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System.Collections.ObjectModel; -using System.Linq; -using System.Threading.Tasks; -using System.Windows; -using Artemis.Core; -using Stylet; - -namespace Artemis.UI.Screens.ProfileEditor.Visualization -{ - public class ProfileDeviceViewModel : CanvasViewModel - { - private bool _addedLeds; - private ArtemisDevice _device; - private ObservableCollection _leds; - - public ProfileDeviceViewModel(ArtemisDevice device) - { - Device = device; - Leds = new ObservableCollection(); - - Task.Run(AddLedsAsync); - } - - public ObservableCollection Leds - { - get => _leds; - set => SetAndNotify(ref _leds, value); - } - - public ArtemisDevice Device - { - get => _device; - set => SetAndNotify(ref _device, value); - } - - public bool AddedLeds - { - get => _addedLeds; - private set => SetAndNotify(ref _addedLeds, value); - } - - public new double X - { - get => Device.X; - set => Device.X = value; - } - - public new double Y - { - get => Device.Y; - set => Device.Y = value; - } - - public int ZIndex - { - get => Device.ZIndex; - set => Device.ZIndex = value; - } - - - public Rect DeviceRectangle => Device.RgbDevice == null - ? new Rect() - : new Rect(X, Y, Device.RgbDevice.Size.Width, Device.RgbDevice.Size.Height); - - /// - /// Update the color of all LEDs if finished adding - /// - public void Update() - { - if (!AddedLeds) - return; - - foreach (var ledViewModel in Leds) - ledViewModel.Update(); - } - - /// - /// Adds LEDs in batches of 5 to avoid UI freezes - /// - /// - private async Task AddLedsAsync() - { - var index = 0; - foreach (var led in Device.Leds.ToList()) - { - Execute.OnUIThreadSync(() => Leds.Add(new ProfileLedViewModel(led))); - if (index % 5 == 0) - await Task.Delay(1); - - index++; - } - - AddedLeds = true; - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileLedView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileLedView.xaml deleted file mode 100644 index 083bfc23c..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileLedView.xaml +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileLedViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileLedViewModel.cs deleted file mode 100644 index 26abba437..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileLedViewModel.cs +++ /dev/null @@ -1,148 +0,0 @@ -using System; -using System.Windows; -using System.Windows.Media; -using Artemis.Core; -using Artemis.UI.Extensions; -using RGB.NET.Core; -using Stylet; -using Color = System.Windows.Media.Color; - -namespace Artemis.UI.Screens.ProfileEditor.Visualization -{ - public class ProfileLedViewModel : PropertyChangedBase - { - private Color _displayColor; - private Geometry _displayGeometry; - private bool _isDimmed; - private bool _isSelected; - private Geometry _strokeGeometry; - - public ProfileLedViewModel(ArtemisLed led) - { - Led = led; - - // Don't want ActualLocation here since rotation is done in XAML - X = led.RgbLed.Location.X * led.RgbLed.Device.Scale.Horizontal; - Y = led.RgbLed.Location.Y * led.RgbLed.Device.Scale.Vertical; - Width = led.RgbLed.ActualSize.Width; - Height = led.RgbLed.ActualSize.Height; - - Execute.PostToUIThread(CreateLedGeometry); - } - - public ArtemisLed Led { get; } - - public double X { get; } - public double Y { get; } - public double Width { get; } - public double Height { get; } - - public bool IsSelected - { - get => _isSelected; - set => SetAndNotify(ref _isSelected, value); - } - - public bool IsDimmed - { - get => _isDimmed; - set => SetAndNotify(ref _isDimmed, value); - } - - public Geometry DisplayGeometry - { - get => _displayGeometry; - private set => SetAndNotify(ref _displayGeometry, value); - } - - public Geometry StrokeGeometry - { - get => _strokeGeometry; - private set => SetAndNotify(ref _strokeGeometry, value); - } - - public Color DisplayColor - { - get => _displayColor; - private set => SetAndNotify(ref _displayColor, value); - } - - public void Update() - { - var newColor = Led.RgbLed.Color.ToMediaColor(); - Execute.PostToUIThread(() => - { - if (!DisplayColor.Equals(newColor)) - DisplayColor = newColor; - }); - } - - private void CreateLedGeometry() - { - switch (Led.RgbLed.Shape) - { - case Shape.Custom: - if (Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keyboard || Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keypad) - CreateCustomGeometry(2.0); - else - CreateCustomGeometry(1.0); - break; - case Shape.Rectangle: - if (Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keyboard || Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keypad) - CreateKeyCapGeometry(); - else - CreateRectangleGeometry(); - break; - case Shape.Circle: - CreateCircleGeometry(); - break; - default: - throw new ArgumentOutOfRangeException(); - } - - // Stroke geometry is the display geometry excluding the inner geometry - StrokeGeometry = DisplayGeometry.GetWidenedPathGeometry(new Pen(null, 1.0), 0.1, ToleranceType.Absolute); - DisplayGeometry.Freeze(); - StrokeGeometry.Freeze(); - } - - private void CreateRectangleGeometry() - { - DisplayGeometry = new RectangleGeometry(new Rect(0.5, 0.5, Width - 1, Height - 1)); - } - - private void CreateCircleGeometry() - { - DisplayGeometry = new EllipseGeometry(new Rect(0.5, 0.5, Width - 1, Height - 1)); - } - - private void CreateKeyCapGeometry() - { - DisplayGeometry = new RectangleGeometry(new Rect(1, 1, Width - 2, Height - 2), 1.6, 1.6); - } - - private void CreateCustomGeometry(double deflateAmount) - { - try - { - DisplayGeometry = Geometry.Combine( - Geometry.Empty, - Geometry.Parse(Led.RgbLed.ShapeData), - GeometryCombineMode.Union, - new TransformGroup - { - Children = new TransformCollection - { - new ScaleTransform(Width - deflateAmount, Height - deflateAmount), - new TranslateTransform(deflateAmount / 2, deflateAmount / 2) - } - } - ); - } - catch (Exception) - { - CreateRectangleGeometry(); - } - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs index c64e1ffcc..0c5b0f566 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs @@ -15,7 +15,7 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.Visualization { - public class ProfileViewModel : ProfileEditorPanelViewModel, IHandle, IHandle + public class ProfileViewModel : Screen, IProfileEditorPanelViewModel, IHandle, IHandle { private readonly IProfileEditorService _profileEditorService; private readonly IProfileLayerVmFactory _profileLayerVmFactory; diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerBrushSettingsWindowView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerBrushSettingsWindowView.xaml similarity index 97% rename from src/Artemis.UI/Screens/ProfileEditor/LayerBrushSettingsWindowView.xaml rename to src/Artemis.UI/Screens/ProfileEditor/Windows/LayerBrushSettingsWindowView.xaml index 6acacbc9a..42507087e 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerBrushSettingsWindowView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerBrushSettingsWindowView.xaml @@ -1,4 +1,4 @@ - { diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerEffectSettingsWindowView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerEffectSettingsWindowView.xaml similarity index 97% rename from src/Artemis.UI/Screens/ProfileEditor/LayerEffectSettingsWindowView.xaml rename to src/Artemis.UI/Screens/ProfileEditor/Windows/LayerEffectSettingsWindowView.xaml index f7f6d2977..1cc495742 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerEffectSettingsWindowView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerEffectSettingsWindowView.xaml @@ -1,4 +1,4 @@ - { From 675486fd7e5f4f025e3a1c522681e3ef2218715c Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 10 Sep 2020 20:23:10 +0200 Subject: [PATCH 06/12] Core - Streamlined public parts of profile creation UI - Started adjusting the VMs for the layer refactor --- .../ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs index 68c462ff1..22467b5ac 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs @@ -52,6 +52,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties SettingsService = settingsService; EffectsViewModel = _layerPropertyVmFactory.EffectsViewModel(this); + Items.Add(EffectsViewModel); + LayerPropertyGroups = new BindableCollection(); PropertyChanged += HandlePropertyTreeIndexChanged; } From 3009a793dddba083db9dbb22b172ad2f43569045 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Fri, 11 Sep 2020 00:04:06 +0200 Subject: [PATCH 07/12] Profile editor - Reimplemented a way to get all keyframe times New implementation does not add clutter to the core --- .../Models/Profile/RenderProfileElement.cs | 3 +- .../Interfaces/IProfileEditorService.cs | 5 +-- .../Services/ProfileEditorService.cs | 19 +++----- .../LayerPropertiesViewModel.cs | 43 +++++++++---------- .../LayerPropertyGroupViewModel.cs | 24 +++++++++++ .../Rails/LayerPropertyTimelineViewModel.cs | 8 ++++ 6 files changed, 63 insertions(+), 39 deletions(-) diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs index 20c938211..4c735e94d 100644 --- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs @@ -204,8 +204,7 @@ namespace Artemis.Core return (TimelinePosition - oldPosition).TotalSeconds; } - - + /// /// Overrides the progress of the element /// diff --git a/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs b/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs index b4185660e..ce1f49197 100644 --- a/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs @@ -84,10 +84,9 @@ namespace Artemis.UI.Shared.Services /// How close the time must be to snap /// Enable snapping to timeline segments /// Enable snapping to the current time of the editor - /// Enable snapping to visible keyframes - /// A keyframe to exclude during keyframe snapping + /// An optional extra list of times to snap to /// - TimeSpan SnapToTimeline(TimeSpan time, TimeSpan tolerance, bool snapToSegments, bool snapToCurrentTime, bool snapToKeyframes, BaseLayerPropertyKeyframe excludedKeyframe = null); + TimeSpan SnapToTimeline(TimeSpan time, TimeSpan tolerance, bool snapToSegments, bool snapToCurrentTime, List snapTimes = null); /// /// If a matching registration is found, creates a new supporting diff --git a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs index bb651f9bd..23004586a 100644 --- a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs @@ -35,7 +35,7 @@ namespace Artemis.UI.Shared.Services public IReadOnlyList RegisteredPropertyEditors => _registeredPropertyEditors.AsReadOnly(); public Profile SelectedProfile { get; private set; } public RenderProfileElement SelectedProfileElement { get; private set; } - public BaseLayerProperty SelectedDataBinding { get; private set; } + public ILayerProperty SelectedDataBinding { get; private set; } public TimeSpan CurrentTime { @@ -128,7 +128,7 @@ namespace Artemis.UI.Shared.Services } } - public void ChangeSelectedDataBinding(BaseLayerProperty layerProperty) + public void ChangeSelectedDataBinding(ILayerProperty layerProperty) { SelectedDataBinding = layerProperty; OnSelectedDataBindingChanged(); @@ -229,7 +229,7 @@ namespace Artemis.UI.Shared.Services } } - public TimeSpan SnapToTimeline(TimeSpan time, TimeSpan tolerance, bool snapToSegments, bool snapToCurrentTime, bool snapToKeyframes, BaseLayerPropertyKeyframe excludedKeyframe = null) + public TimeSpan SnapToTimeline(TimeSpan time, TimeSpan tolerance, bool snapToSegments, bool snapToCurrentTime, List snapTimes = null) { if (snapToSegments) { @@ -254,17 +254,12 @@ namespace Artemis.UI.Shared.Services return SelectedProfileElement.StartSegmentLength; } - if (snapToKeyframes) + if (snapTimes != null) { - // Get all visible keyframes - var keyframes = SelectedProfileElement.GetAllKeyframes() - .Where(k => k != excludedKeyframe && SelectedProfileElement.IsPropertyGroupExpanded(k.BaseLayerProperty.Parent)) - .ToList(); - // Find the closest keyframe - var closeKeyframe = keyframes.FirstOrDefault(k => Math.Abs(time.TotalMilliseconds - k.Position.TotalMilliseconds) < tolerance.TotalMilliseconds); - if (closeKeyframe != null) - return closeKeyframe.Position; + var closeSnapTime = snapTimes.FirstOrDefault(s => Math.Abs(time.TotalMilliseconds - s.TotalMilliseconds) < tolerance.TotalMilliseconds); + if (closeSnapTime != TimeSpan.Zero) + return closeSnapTime; } return time; diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs index 22467b5ac..dd0743d08 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs @@ -16,6 +16,7 @@ using Artemis.UI.Shared; using Artemis.UI.Shared.Services; using GongSolutions.Wpf.DragDrop; using Stylet; +using static Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree.LayerPropertyGroupTreeViewModel.LayerPropertyGroupType; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties { @@ -38,7 +39,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties private TimelineViewModel _timelineViewModel; private TreeViewModel _treeViewModel; - public LayerPropertiesViewModel(IProfileEditorService profileEditorService, + public LayerPropertiesViewModel(IProfileEditorService profileEditorService, ICoreService coreService, ISettingsService settingsService, ILayerPropertyVmFactory layerPropertyVmFactory, @@ -397,9 +398,14 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties private void SortProperties() { // Get all non-effect properties - var nonEffectProperties = LayerPropertyGroups.Where(l => l.GroupType != LayerEffectRoot).ToList(); + var nonEffectProperties = LayerPropertyGroups + .Where(l => l.LayerPropertyGroupTreeViewModel.GroupType != LayerEffectRoot) + .ToList(); // Order the effects - var effectProperties = LayerPropertyGroups.Where(l => l.GroupType == LayerEffectRoot).OrderBy(l => l.LayerPropertyGroup.LayerEffect.Order).ToList(); + var effectProperties = LayerPropertyGroups + .Where(l => l.LayerPropertyGroupTreeViewModel.GroupType == LayerEffectRoot) + .OrderBy(l => l.LayerPropertyGroup.LayerEffect.Order) + .ToList(); // Put the non-effect properties in front for (var index = 0; index < nonEffectProperties.Count; index++) @@ -435,7 +441,9 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties var source = dropInfo.Data as LayerPropertyGroupViewModel; var target = dropInfo.TargetItem as LayerPropertyGroupViewModel; - if (source == target || target?.GroupType != LayerEffectRoot || source?.GroupType != LayerEffectRoot) + if (source == target || + target?.LayerPropertyGroupTreeViewModel.GroupType != LayerEffectRoot || + source?.LayerPropertyGroupTreeViewModel.GroupType != LayerEffectRoot) return; dropInfo.DropTargetAdorner = DropTargetAdorners.Insert; @@ -452,7 +460,9 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties var source = dropInfo.Data as LayerPropertyGroupViewModel; var target = dropInfo.TargetItem as LayerPropertyGroupViewModel; - if (source == target || target?.GroupType != LayerEffectRoot || source?.GroupType != LayerEffectRoot) + if (source == target || + target?.LayerPropertyGroupTreeViewModel.GroupType != LayerEffectRoot || + source?.LayerPropertyGroupTreeViewModel.GroupType != LayerEffectRoot) return; if (dropInfo.InsertPosition == RelativeInsertPosition.BeforeTargetItem) @@ -481,7 +491,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties private void ApplyCurrentEffectsOrder() { var order = 1; - foreach (var groupViewModel in LayerPropertyGroups.Where(p => p.GroupType == LayerEffectRoot)) + foreach (var groupViewModel in LayerPropertyGroups.Where(p => p.LayerPropertyGroupTreeViewModel.GroupType == LayerEffectRoot)) { groupViewModel.UpdateOrder(order); order++; @@ -551,16 +561,13 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties private TimeSpan CalculateEndTime() { - if (!(ProfileEditorService.SelectedProfileElement is Layer layer)) - return TimeSpan.MaxValue; - - var keyframes = GetKeyframes(false); + var keyframeTimes = LayerPropertyGroups.SelectMany(g => g.GetAllKeyframePositions(false)).ToList(); // If there are no keyframes, don't stop at all - if (!keyframes.Any()) + if (!keyframeTimes.Any()) return TimeSpan.MaxValue; // If there are keyframes, stop after the last keyframe + 10 sec - return keyframes.Max(k => k.Position).Add(TimeSpan.FromSeconds(10)); + return keyframeTimes.Max().Add(TimeSpan.FromSeconds(10)); } private void CoreServiceOnFrameRendering(object sender, FrameRenderingEventArgs e) @@ -617,7 +624,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties // If holding down shift, snap to the closest segment or keyframe if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) { - var snappedTime = ProfileEditorService.SnapToTimeline(newTime, TimeSpan.FromMilliseconds(1000f / ProfileEditorService.PixelsPerSecond * 5), true, false, true); + var snapTimes = LayerPropertyGroups.SelectMany(g => g.GetAllKeyframePositions(true)).ToList(); + var snappedTime = ProfileEditorService.SnapToTimeline(newTime, TimeSpan.FromMilliseconds(1000f / ProfileEditorService.PixelsPerSecond * 5), true, false, snapTimes); ProfileEditorService.CurrentTime = snappedTime; return; } @@ -634,15 +642,6 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties } } - private List GetKeyframes(bool visibleOnly) - { - var result = new List(); - foreach (var layerPropertyGroupViewModel in LayerPropertyGroups) - result.AddRange(layerPropertyGroupViewModel.GetKeyframes(visibleOnly)); - - return result; - } - #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs index 3f87b3479..ae3e7091a 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Artemis.Core; using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree; @@ -69,5 +70,28 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties disposableChild.Dispose(); } } + + public void UpdateOrder(int order) + { + LayerPropertyGroup.LayerEffect.Order = order; + NotifyOfPropertyChange(nameof(IsExpanded)); + } + + public List GetAllKeyframePositions(bool expandedOnly) + { + var result = new List(); + if (expandedOnly == IsExpanded) + return result; + + foreach (var child in Children) + { + if (child is LayerPropertyViewModel layerPropertyViewModel) + result.AddRange(layerPropertyViewModel.LayerPropertyTimelineViewModel.GetAllKeyframePositions()); + else if (child is LayerPropertyGroupViewModel layerPropertyGroupViewModel) + result.AddRange(layerPropertyGroupViewModel.GetAllKeyframePositions(expandedOnly)); + } + + return result; + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Rails/LayerPropertyTimelineViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Rails/LayerPropertyTimelineViewModel.cs index 370dc0904..36a6e00c8 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Rails/LayerPropertyTimelineViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Rails/LayerPropertyTimelineViewModel.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using Artemis.Core; using Stylet; @@ -15,6 +17,11 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties LayerPropertyViewModel = layerPropertyViewModel; } + public List GetAllKeyframePositions() + { + return LayerProperty.Keyframes.Select(k => k.Position).ToList(); + } + public void Dispose() { } @@ -22,5 +29,6 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties public interface ILayerPropertyTimelineViewModel : IScreen, IDisposable { + List GetAllKeyframePositions(); } } \ No newline at end of file From 13a006ba48085ec50fd5071119805148e4d08999 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 11 Sep 2020 19:51:24 +0200 Subject: [PATCH 08/12] Profile editor - Reimplementing the timeline --- .../Ninject/Factories/IVMFactory.cs | 5 +- .../DataBindingsViewModelInstanceProvider.cs | 14 ++ .../LayerPropertiesViewModel.cs | 30 ++-- .../LayerPropertyGroupViewModel.cs | 15 +- .../LayerProperties/LayerPropertyViewModel.cs | 17 +- .../Rails/LayerPropertyTimelineViewModel.cs | 34 ---- .../Timeline/Rails/TimelineGroupViewModel.cs | 17 -- .../Timeline/TimelineEasingViewModel.cs | 23 +-- ...yGroupView.xaml => TimelineGroupView.xaml} | 25 ++- .../Timeline/TimelineGroupViewModel.cs | 48 ++++++ .../Timeline/TimelineKeyframeViewModel.cs | 151 +++++++++--------- .../TimelinePropertyGroupViewModel.cs | 59 ------- .../Timeline/TimelinePropertyView.xaml | 3 +- .../Timeline/TimelinePropertyViewModel.cs | 99 +++--------- .../Timeline/TimelineViewModel.cs | 19 +-- ...yGroupTreeView.xaml => TreeGroupView.xaml} | 4 +- ...TreeViewModel.cs => TreeGroupViewModel.cs} | 4 +- ...rtyTreeView.xaml => TreePropertyView.xaml} | 2 +- ...eViewModel.cs => TreePropertyViewModel.cs} | 6 +- .../LayerProperties/Tree/TreeView.xaml | 4 +- 20 files changed, 231 insertions(+), 348 deletions(-) create mode 100644 src/Artemis.UI/Ninject/InstanceProviders/DataBindingsViewModelInstanceProvider.cs delete mode 100644 src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Rails/LayerPropertyTimelineViewModel.cs delete mode 100644 src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Rails/TimelineGroupViewModel.cs rename src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/{TimelinePropertyGroupView.xaml => TimelineGroupView.xaml} (82%) create mode 100644 src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineGroupViewModel.cs delete mode 100644 src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupViewModel.cs rename src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/{LayerPropertyGroupTreeView.xaml => TreeGroupView.xaml} (98%) rename src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/{LayerPropertyGroupTreeViewModel.cs => TreeGroupViewModel.cs} (98%) rename src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/{LayerPropertyTreeView.xaml => TreePropertyView.xaml} (98%) rename src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/{LayerPropertyTreeViewModel.cs => TreePropertyViewModel.cs} (90%) diff --git a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs index 04c3cecef..86b82e29e 100644 --- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -80,8 +80,9 @@ namespace Artemis.UI.Ninject.Factories { LayerPropertyViewModel LayerPropertyViewModel(ILayerProperty layerProperty); LayerPropertyGroupViewModel LayerPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup); - LayerPropertyTreeViewModel LayerPropertyGroupViewModel(LayerProperty layerProperty); - LayerPropertyGroupTreeViewModel LayerPropertyGroupTreeViewModel(LayerPropertyGroupViewModel layerPropertyGroupViewModel); + TreePropertyViewModel LayerPropertyGroupViewModel(LayerProperty layerProperty); + TreeGroupViewModel TreeGroupViewModel(LayerPropertyGroupViewModel layerPropertyGroupViewModel); + TimelineGroupViewModel TimelineGroupViewModel(LayerPropertyGroupViewModel layerPropertyGroupViewModel); LayerPropertyGroupViewModel LayerPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, PropertyGroupDescriptionAttribute propertyGroupDescription); TreeViewModel TreeViewModel(LayerPropertiesViewModel layerPropertiesViewModel, BindableCollection layerPropertyGroups); EffectsViewModel EffectsViewModel(LayerPropertiesViewModel layerPropertiesViewModel); diff --git a/src/Artemis.UI/Ninject/InstanceProviders/DataBindingsViewModelInstanceProvider.cs b/src/Artemis.UI/Ninject/InstanceProviders/DataBindingsViewModelInstanceProvider.cs new file mode 100644 index 000000000..5df778f89 --- /dev/null +++ b/src/Artemis.UI/Ninject/InstanceProviders/DataBindingsViewModelInstanceProvider.cs @@ -0,0 +1,14 @@ +using System; +using System.Reflection; +using Ninject.Extensions.Factory; + +namespace Artemis.UI.Ninject.InstanceProviders +{ + public class DataBindingsViewModelInstanceProvider : StandardInstanceProvider + { + protected override Type GetType(MethodInfo methodInfo, object[] arguments) + { + return base.GetType(methodInfo, arguments); + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs index dd0743d08..e3a6c0974 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs @@ -16,7 +16,7 @@ using Artemis.UI.Shared; using Artemis.UI.Shared.Services; using GongSolutions.Wpf.DragDrop; using Stylet; -using static Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree.LayerPropertyGroupTreeViewModel.LayerPropertyGroupType; +using static Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree.TreeGroupViewModel.LayerPropertyGroupType; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties { @@ -246,7 +246,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties { RightSideIndex = 1; DataBindingsViewModel?.Dispose(); - DataBindingsViewModel = _dataBindingsVmFactory.DataBindingsViewModel(ProfileEditorService.SelectedDataBinding); + // TODO + // DataBindingsViewModel = _dataBindingsVmFactory.DataBindingsViewModel(ProfileEditorService.SelectedDataBinding); } else RightSideIndex = 0; @@ -357,7 +358,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties } SortProperties(); - UpdateKeyframes(); + TimelineViewModel.Update(); } private void ApplyEffects() @@ -392,18 +393,18 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties } SortProperties(); - UpdateKeyframes(); + TimelineViewModel.Update(); } private void SortProperties() { // Get all non-effect properties var nonEffectProperties = LayerPropertyGroups - .Where(l => l.LayerPropertyGroupTreeViewModel.GroupType != LayerEffectRoot) + .Where(l => l.TreeGroupViewModel.GroupType != LayerEffectRoot) .ToList(); // Order the effects var effectProperties = LayerPropertyGroups - .Where(l => l.LayerPropertyGroupTreeViewModel.GroupType == LayerEffectRoot) + .Where(l => l.TreeGroupViewModel.GroupType == LayerEffectRoot) .OrderBy(l => l.LayerPropertyGroup.LayerEffect.Order) .ToList(); @@ -421,12 +422,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties LayerPropertyGroups.Move(LayerPropertyGroups.IndexOf(layerPropertyGroupViewModel), index + nonEffectProperties.Count); } } - - private void UpdateKeyframes() - { - TimelineViewModel.Update(); - } - + #endregion #region Drag and drop @@ -442,8 +438,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties var target = dropInfo.TargetItem as LayerPropertyGroupViewModel; if (source == target || - target?.LayerPropertyGroupTreeViewModel.GroupType != LayerEffectRoot || - source?.LayerPropertyGroupTreeViewModel.GroupType != LayerEffectRoot) + target?.TreeGroupViewModel.GroupType != LayerEffectRoot || + source?.TreeGroupViewModel.GroupType != LayerEffectRoot) return; dropInfo.DropTargetAdorner = DropTargetAdorners.Insert; @@ -461,8 +457,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties var target = dropInfo.TargetItem as LayerPropertyGroupViewModel; if (source == target || - target?.LayerPropertyGroupTreeViewModel.GroupType != LayerEffectRoot || - source?.LayerPropertyGroupTreeViewModel.GroupType != LayerEffectRoot) + target?.TreeGroupViewModel.GroupType != LayerEffectRoot || + source?.TreeGroupViewModel.GroupType != LayerEffectRoot) return; if (dropInfo.InsertPosition == RelativeInsertPosition.BeforeTargetItem) @@ -491,7 +487,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties private void ApplyCurrentEffectsOrder() { var order = 1; - foreach (var groupViewModel in LayerPropertyGroups.Where(p => p.LayerPropertyGroupTreeViewModel.GroupType == LayerEffectRoot)) + foreach (var groupViewModel in LayerPropertyGroups.Where(p => p.TreeGroupViewModel.GroupType == LayerEffectRoot)) { groupViewModel.UpdateOrder(order); order++; diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs index ae3e7091a..09f4e5132 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Artemis.Core; using Artemis.UI.Ninject.Factories; +using Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline; using Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree; using Stylet; @@ -16,13 +17,17 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties _layerPropertyVmFactory = layerPropertyVmFactory; LayerPropertyGroup = layerPropertyGroup; - LayerPropertyGroupTreeViewModel = layerPropertyVmFactory.LayerPropertyGroupTreeViewModel(this); + Children = new BindableCollection(); + + TreeGroupViewModel = layerPropertyVmFactory.TreeGroupViewModel(this); + TimelineGroupViewModel = layerPropertyVmFactory.TimelineGroupViewModel(this); PopulateChildren(); } public LayerPropertyGroup LayerPropertyGroup { get; } - public LayerPropertyGroupTreeViewModel LayerPropertyGroupTreeViewModel { get; } - public BindableCollection Children { get; set; } + public TreeGroupViewModel TreeGroupViewModel { get; } + public TimelineGroupViewModel TimelineGroupViewModel { get; } + public BindableCollection Children { get; } public bool IsVisible => !LayerPropertyGroup.IsHidden; @@ -51,7 +56,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties { var layerPropertyViewModel = _layerPropertyVmFactory.LayerPropertyViewModel(layerProperty); // After creation ensure a supported input VM was found, if not, discard the VM - if (!layerPropertyViewModel.LayerPropertyTreeViewModel.HasPropertyInputViewModel) + if (!layerPropertyViewModel.TreePropertyViewModel.HasPropertyInputViewModel) layerPropertyViewModel.Dispose(); else Children.Add(layerPropertyViewModel); @@ -86,7 +91,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties foreach (var child in Children) { if (child is LayerPropertyViewModel layerPropertyViewModel) - result.AddRange(layerPropertyViewModel.LayerPropertyTimelineViewModel.GetAllKeyframePositions()); + result.AddRange(layerPropertyViewModel.TimelinePropertyViewModel.GetAllKeyframePositions()); else if (child is LayerPropertyGroupViewModel layerPropertyGroupViewModel) result.AddRange(layerPropertyGroupViewModel.GetAllKeyframePositions(expandedOnly)); } diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs index f436b7f0d..50eb8cdcd 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs @@ -1,5 +1,6 @@ using System; using Artemis.Core; +using Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline; using Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree; using Ninject; using Ninject.Parameters; @@ -14,24 +15,24 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties LayerProperty = layerProperty; var parameter = new ConstructorArgument("layerProperty", LayerProperty); - var treeViewModelType = typeof(LayerPropertyTreeViewModel<>).MakeGenericType(layerProperty.GetType().GetGenericArguments()); - var timelineViewModelType = typeof(LayerPropertyTimelineViewModel<>).MakeGenericType(layerProperty.GetType().GetGenericArguments()); + var treeViewModelType = typeof(TreePropertyViewModel<>).MakeGenericType(layerProperty.GetType().GetGenericArguments()); + var timelineViewModelType = typeof(TimelinePropertyViewModel<>).MakeGenericType(layerProperty.GetType().GetGenericArguments()); - LayerPropertyTreeViewModel = (ILayerPropertyTreeViewModel) kernel.Get(treeViewModelType, parameter); - LayerPropertyTimelineViewModel = (ILayerPropertyTimelineViewModel) kernel.Get(timelineViewModelType, parameter); + TreePropertyViewModel = (ITreePropertyViewModel) kernel.Get(treeViewModelType, parameter); + TimelinePropertyViewModel = (ITimelinePropertyViewModel) kernel.Get(timelineViewModelType, parameter); } public ILayerProperty LayerProperty { get; } - public ILayerPropertyTreeViewModel LayerPropertyTreeViewModel { get; } - public ILayerPropertyTimelineViewModel LayerPropertyTimelineViewModel { get; } + public ITreePropertyViewModel TreePropertyViewModel { get; } + public ITimelinePropertyViewModel TimelinePropertyViewModel { get; } public bool IsVisible { get; set; } public bool IsExpanded { get; set; } public void Dispose() { - LayerPropertyTreeViewModel?.Dispose(); - LayerPropertyTimelineViewModel?.Dispose(); + TreePropertyViewModel?.Dispose(); + TimelinePropertyViewModel?.Dispose(); } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Rails/LayerPropertyTimelineViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Rails/LayerPropertyTimelineViewModel.cs deleted file mode 100644 index 36a6e00c8..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Rails/LayerPropertyTimelineViewModel.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Artemis.Core; -using Stylet; - -namespace Artemis.UI.Screens.ProfileEditor.LayerProperties -{ - public class LayerPropertyTimelineViewModel : Screen, ILayerPropertyTimelineViewModel - { - public LayerProperty LayerProperty { get; } - public LayerPropertyViewModel LayerPropertyViewModel { get; } - - public LayerPropertyTimelineViewModel(LayerProperty layerProperty, LayerPropertyViewModel layerPropertyViewModel) - { - LayerProperty = layerProperty; - LayerPropertyViewModel = layerPropertyViewModel; - } - - public List GetAllKeyframePositions() - { - return LayerProperty.Keyframes.Select(k => k.Position).ToList(); - } - - public void Dispose() - { - } - } - - public interface ILayerPropertyTimelineViewModel : IScreen, IDisposable - { - List GetAllKeyframePositions(); - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Rails/TimelineGroupViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Rails/TimelineGroupViewModel.cs deleted file mode 100644 index fb39a056f..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Rails/TimelineGroupViewModel.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Artemis.Core; - -namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline.Rails -{ - public class TimelineGroupViewModel - { - public LayerPropertyGroup LayerPropertyGroup { get; } - - public TimelineGroupViewModel(LayerPropertyGroup layerPropertyGroup) - { - LayerPropertyGroup = layerPropertyGroup; - } - } -} diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineEasingViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineEasingViewModel.cs index a89e161cb..859643892 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineEasingViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineEasingViewModel.cs @@ -1,4 +1,5 @@ -using System.Windows; +using System; +using System.Windows; using System.Windows.Media; using Artemis.Core; using Humanizer; @@ -7,17 +8,11 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline { public class TimelineEasingViewModel { - private readonly TimelineKeyframeViewModel _keyframeViewModel; private bool _isEasingModeSelected; - public TimelineEasingViewModel(TimelineKeyframeViewModel keyframeViewModel, Easings.Functions easingFunction) + public TimelineEasingViewModel(Easings.Functions easingFunction, bool isSelected) { - // Can be null if used by DataBindingViewModel because I'm lazy - if (keyframeViewModel != null) - { - _keyframeViewModel = keyframeViewModel; - _isEasingModeSelected = keyframeViewModel.BaseLayerPropertyKeyframe.EasingFunction == easingFunction; - } + _isEasingModeSelected = isSelected; EasingFunction = easingFunction; Description = easingFunction.Humanize(); @@ -41,9 +36,15 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline set { _isEasingModeSelected = value; - if (_isEasingModeSelected) - _keyframeViewModel.SelectEasingMode(this); + if (value) OnEasingModeSelected(); } } + + public event EventHandler EasingModeSelected; + + protected virtual void OnEasingModeSelected() + { + EasingModeSelected?.Invoke(this, EventArgs.Empty); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineGroupView.xaml similarity index 82% rename from src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupView.xaml rename to src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineGroupView.xaml index 96852f448..0715f4cb5 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineGroupView.xaml @@ -1,14 +1,14 @@ - @@ -20,7 +20,7 @@ @@ -59,14 +59,13 @@ - - + + - - + + - - \ No newline at end of file + diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineGroupViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineGroupViewModel.cs new file mode 100644 index 000000000..9e40b1bb8 --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineGroupViewModel.cs @@ -0,0 +1,48 @@ +using System; +using System.Linq; +using Artemis.Core; +using Artemis.UI.Shared.Services; +using Stylet; + +namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline +{ + public class TimelineGroupViewModel : IDisposable + { + private readonly IProfileEditorService _profileEditorService; + + public TimelineGroupViewModel(LayerPropertyGroupViewModel layerPropertyGroupViewModel, IProfileEditorService profileEditorService) + { + _profileEditorService = profileEditorService; + + LayerPropertyGroupViewModel = layerPropertyGroupViewModel; + LayerPropertyGroup = LayerPropertyGroupViewModel.LayerPropertyGroup; + KeyframePositions = new BindableCollection(); + + _profileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged; + UpdateKeyframePositions(); + } + + public LayerPropertyGroupViewModel LayerPropertyGroupViewModel { get; } + public LayerPropertyGroup LayerPropertyGroup { get; } + + public BindableCollection KeyframePositions { get; } + + public void Dispose() + { + _profileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged; + } + + private void ProfileEditorServiceOnPixelsPerSecondChanged(object? sender, EventArgs e) + { + UpdateKeyframePositions(); + } + + public void UpdateKeyframePositions() + { + KeyframePositions.Clear(); + KeyframePositions.AddRange(LayerPropertyGroupViewModel + .GetAllKeyframePositions(false) + .Select(p => p.TotalSeconds * _profileEditorService.PixelsPerSecond)); + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs index 34210bdae..c586ce613 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs @@ -7,73 +7,23 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline { - public class TimelineKeyframeViewModel : TimelineKeyframeViewModel + public class TimelineKeyframeViewModel : Screen, IDisposable { private readonly IProfileEditorService _profileEditorService; - public TimelineKeyframeViewModel(IProfileEditorService profileEditorService, LayerPropertyKeyframe layerPropertyKeyframe) - : base(profileEditorService, layerPropertyKeyframe) - { - _profileEditorService = profileEditorService; - LayerPropertyKeyframe = layerPropertyKeyframe; - } - - public LayerPropertyKeyframe LayerPropertyKeyframe { get; } - - #region Context menu actions - - public override void Copy() - { - var newKeyframe = new LayerPropertyKeyframe( - LayerPropertyKeyframe.Value, - LayerPropertyKeyframe.Position, - LayerPropertyKeyframe.EasingFunction, - LayerPropertyKeyframe.LayerProperty - ); - // If possible, shift the keyframe to the right by 11 pixels - var desiredPosition = newKeyframe.Position + TimeSpan.FromMilliseconds(1000f / _profileEditorService.PixelsPerSecond * 11); - if (desiredPosition <= newKeyframe.LayerProperty.ProfileElement.TimelineLength) - newKeyframe.Position = desiredPosition; - // Otherwise if possible shift it to the left by 11 pixels - else - { - desiredPosition = newKeyframe.Position - TimeSpan.FromMilliseconds(1000f / _profileEditorService.PixelsPerSecond * 11); - if (desiredPosition > TimeSpan.Zero) - newKeyframe.Position = desiredPosition; - } - - LayerPropertyKeyframe.LayerProperty.AddKeyframe(newKeyframe); - _profileEditorService.UpdateSelectedProfileElement(); - } - - public override void Delete() - { - LayerPropertyKeyframe.LayerProperty.RemoveKeyframe(LayerPropertyKeyframe); - _profileEditorService.UpdateSelectedProfileElement(); - } - - #endregion - } - - public abstract class TimelineKeyframeViewModel : PropertyChangedBase, IDisposable - { - private readonly IProfileEditorService _profileEditorService; private BindableCollection _easingViewModels; private bool _isSelected; - private int _pixelsPerSecond; private string _timestamp; private double _x; - protected TimelineKeyframeViewModel(IProfileEditorService profileEditorService, BaseLayerPropertyKeyframe baseLayerPropertyKeyframe) + public TimelineKeyframeViewModel(LayerPropertyKeyframe layerPropertyKeyframe, IProfileEditorService profileEditorService) { _profileEditorService = profileEditorService; - BaseLayerPropertyKeyframe = baseLayerPropertyKeyframe; - EasingViewModels = new BindableCollection(); - - BaseLayerPropertyKeyframe.PropertyChanged += BaseLayerPropertyKeyframeOnPropertyChanged; + LayerPropertyKeyframe = layerPropertyKeyframe; + LayerPropertyKeyframe.PropertyChanged += LayerPropertyKeyframeOnPropertyChanged; } - public BaseLayerPropertyKeyframe BaseLayerPropertyKeyframe { get; } + public LayerPropertyKeyframe LayerPropertyKeyframe { get; } public BindableCollection EasingViewModels { @@ -101,42 +51,51 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline public void Dispose() { - BaseLayerPropertyKeyframe.PropertyChanged -= BaseLayerPropertyKeyframeOnPropertyChanged; + LayerPropertyKeyframe.PropertyChanged -= LayerPropertyKeyframeOnPropertyChanged; + + foreach (var timelineEasingViewModel in EasingViewModels) + timelineEasingViewModel.EasingModeSelected -= TimelineEasingViewModelOnEasingModeSelected; } - public void Update(int pixelsPerSecond) + public void Update() { - _pixelsPerSecond = pixelsPerSecond; - - X = pixelsPerSecond * BaseLayerPropertyKeyframe.Position.TotalSeconds; - Timestamp = $"{Math.Floor(BaseLayerPropertyKeyframe.Position.TotalSeconds):00}.{BaseLayerPropertyKeyframe.Position.Milliseconds:000}"; + X = _profileEditorService.PixelsPerSecond * LayerPropertyKeyframe.Position.TotalSeconds; + Timestamp = $"{Math.Floor(LayerPropertyKeyframe.Position.TotalSeconds):00}.{LayerPropertyKeyframe.Position.Milliseconds:000}"; } - public abstract void Copy(); - - public abstract void Delete(); - - private void BaseLayerPropertyKeyframeOnPropertyChanged(object sender, PropertyChangedEventArgs e) + private void LayerPropertyKeyframeOnPropertyChanged(object sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(BaseLayerPropertyKeyframe.Position)) - Update(_pixelsPerSecond); + if (e.PropertyName == nameof(LayerPropertyKeyframe.Position)) + Update(); } #region Easing public void CreateEasingViewModels() { - EasingViewModels.AddRange(Enum.GetValues(typeof(Easings.Functions)).Cast().Select(v => new TimelineEasingViewModel(this, v))); + if (EasingViewModels.Any()) + return; + + EasingViewModels.AddRange(Enum.GetValues(typeof(Easings.Functions)) + .Cast() + .Select(e => new TimelineEasingViewModel(e, e == LayerPropertyKeyframe.EasingFunction))); + + foreach (var timelineEasingViewModel in EasingViewModels) + timelineEasingViewModel.EasingModeSelected += TimelineEasingViewModelOnEasingModeSelected; + } + + private void TimelineEasingViewModelOnEasingModeSelected(object sender, EventArgs e) + { + SelectEasingMode((TimelineEasingViewModel) sender); } public void SelectEasingMode(TimelineEasingViewModel easingViewModel) { - BaseLayerPropertyKeyframe.EasingFunction = easingViewModel.EasingFunction; + LayerPropertyKeyframe.EasingFunction = easingViewModel.EasingFunction; // Set every selection to false except on the VM that made the change foreach (var propertyTrackEasingViewModel in EasingViewModels.Where(vm => vm != easingViewModel)) propertyTrackEasingViewModel.IsEasingModeSelected = false; - _profileEditorService.UpdateSelectedProfileElement(); } @@ -151,7 +110,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline _offset = null; } - public void SaveOffsetToKeyframe(TimelineKeyframeViewModel keyframeViewModel) + public void SaveOffsetToKeyframe(TimelineKeyframeViewModel keyframeViewModel) { if (keyframeViewModel == this) { @@ -162,27 +121,61 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline if (_offset != null) return; - _offset = BaseLayerPropertyKeyframe.Position - keyframeViewModel.BaseLayerPropertyKeyframe.Position; + _offset = LayerPropertyKeyframe.Position - keyframeViewModel.LayerPropertyKeyframe.Position; } - public void ApplyOffsetToKeyframe(TimelineKeyframeViewModel keyframeViewModel) + public void ApplyOffsetToKeyframe(TimelineKeyframeViewModel keyframeViewModel) { if (keyframeViewModel == this || _offset == null) return; - UpdatePosition(keyframeViewModel.BaseLayerPropertyKeyframe.Position + _offset.Value); + UpdatePosition(keyframeViewModel.LayerPropertyKeyframe.Position + _offset.Value); } public void UpdatePosition(TimeSpan position) { if (position < TimeSpan.Zero) - BaseLayerPropertyKeyframe.Position = TimeSpan.Zero; + LayerPropertyKeyframe.Position = TimeSpan.Zero; else if (position > _profileEditorService.SelectedProfileElement.TimelineLength) - BaseLayerPropertyKeyframe.Position = _profileEditorService.SelectedProfileElement.TimelineLength; + LayerPropertyKeyframe.Position = _profileEditorService.SelectedProfileElement.TimelineLength; else - BaseLayerPropertyKeyframe.Position = position; + LayerPropertyKeyframe.Position = position; - Update(_pixelsPerSecond); + Update(); + } + + #endregion + + #region Context menu actions + + public void Copy() + { + var newKeyframe = new LayerPropertyKeyframe( + LayerPropertyKeyframe.Value, + LayerPropertyKeyframe.Position, + LayerPropertyKeyframe.EasingFunction, + LayerPropertyKeyframe.LayerProperty + ); + // If possible, shift the keyframe to the right by 11 pixels + var desiredPosition = newKeyframe.Position + TimeSpan.FromMilliseconds(1000f / _profileEditorService.PixelsPerSecond * 11); + if (desiredPosition <= newKeyframe.LayerProperty.ProfileElement.TimelineLength) + newKeyframe.Position = desiredPosition; + // Otherwise if possible shift it to the left by 11 pixels + else + { + desiredPosition = newKeyframe.Position - TimeSpan.FromMilliseconds(1000f / _profileEditorService.PixelsPerSecond * 11); + if (desiredPosition > TimeSpan.Zero) + newKeyframe.Position = desiredPosition; + } + + LayerPropertyKeyframe.LayerProperty.AddKeyframe(newKeyframe); + _profileEditorService.UpdateSelectedProfileElement(); + } + + public void Delete() + { + LayerPropertyKeyframe.LayerProperty.RemoveKeyframe(LayerPropertyKeyframe); + _profileEditorService.UpdateSelectedProfileElement(); } #endregion diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupViewModel.cs deleted file mode 100644 index ea05a499d..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupViewModel.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.ComponentModel; -using System.Linq; -using Artemis.UI.Screens.ProfileEditor.LayerProperties.Abstract; -using Artemis.UI.Shared.Services; -using Stylet; - -namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline -{ - public class TimelinePropertyGroupViewModel : PropertyChangedBase - { - private readonly IProfileEditorService _profileEditorService; - private BindableCollection _timelineKeyframeViewModels; - - public TimelinePropertyGroupViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel, IProfileEditorService profileEditorService) - { - _profileEditorService = profileEditorService; - LayerPropertyGroupViewModel = (LayerPropertyGroupViewModel) layerPropertyBaseViewModel; - TimelineKeyframeViewModels = new BindableCollection(); - - LayerPropertyGroupViewModel.ProfileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged; - LayerPropertyGroupViewModel.PropertyChanged += LayerPropertyGroupViewModelOnPropertyChanged; - UpdateKeyframes(); - } - - public LayerPropertyGroupViewModel LayerPropertyGroupViewModel { get; } - - public BindableCollection TimelineKeyframeViewModels - { - get => _timelineKeyframeViewModels; - set => SetAndNotify(ref _timelineKeyframeViewModels, value); - } - - public void UpdateKeyframes() - { - TimelineKeyframeViewModels.Clear(); - TimelineKeyframeViewModels.AddRange(LayerPropertyGroupViewModel.GetKeyframes(false) - .Select(k => LayerPropertyGroupViewModel.ProfileEditorService.PixelsPerSecond * k.Position.TotalSeconds)); - } - - - public void Dispose() - { - LayerPropertyGroupViewModel.ProfileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged; - LayerPropertyGroupViewModel.PropertyChanged -= LayerPropertyGroupViewModelOnPropertyChanged; - } - - private void LayerPropertyGroupViewModelOnPropertyChanged(object sender, PropertyChangedEventArgs e) - { - if (e.PropertyName == nameof(LayerPropertyGroupViewModel.IsExpanded)) - UpdateKeyframes(); - } - - private void ProfileEditorServiceOnPixelsPerSecondChanged(object sender, EventArgs e) - { - UpdateKeyframes(); - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyView.xaml index 5fcb44d45..6438ce3e8 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyView.xaml @@ -9,12 +9,11 @@ xmlns:timeline="clr-namespace:Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" - d:DataContext="{d:DesignInstance local:TimelinePropertyViewModel}" Visibility="{Binding LayerPropertyBaseViewModel.IsVisible, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" MinWidth="{Binding Width}" HorizontalAlignment="Stretch"> - diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs index 53c0a6c5a..320bf9099 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs @@ -1,108 +1,61 @@ using System; +using System.Collections.Generic; using System.Linq; -using Artemis.UI.Screens.ProfileEditor.LayerProperties.Abstract; +using Artemis.Core; using Artemis.UI.Shared.Services; using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline { - public class TimelinePropertyViewModel : TimelinePropertyViewModel + public class TimelinePropertyViewModel : Conductor>.Collection.AllActive, ITimelinePropertyViewModel { private readonly IProfileEditorService _profileEditorService; + public LayerProperty LayerProperty { get; } + public LayerPropertyViewModel LayerPropertyViewModel { get; } - public TimelinePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel, IProfileEditorService profileEditorService) : base(layerPropertyBaseViewModel) + public TimelinePropertyViewModel(LayerProperty layerProperty, LayerPropertyViewModel layerPropertyViewModel, IProfileEditorService profileEditorService) { _profileEditorService = profileEditorService; - LayerPropertyViewModel = (LayerPropertyViewModel) layerPropertyBaseViewModel; - LayerPropertyViewModel.LayerProperty.KeyframeAdded += LayerPropertyOnKeyframeModified; - LayerPropertyViewModel.LayerProperty.KeyframeRemoved += LayerPropertyOnKeyframeModified; - LayerPropertyViewModel.LayerProperty.KeyframesToggled += LayerPropertyOnKeyframeModified; - _profileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged; + LayerProperty = layerProperty; + LayerPropertyViewModel = layerPropertyViewModel; } - public LayerPropertyViewModel LayerPropertyViewModel { get; } + public List GetAllKeyframePositions() + { + return LayerProperty.Keyframes.Select(k => k.Position).ToList(); + } - public override void UpdateKeyframes() + private void UpdateKeyframes() { // Only show keyframes if they are enabled - if (LayerPropertyViewModel.LayerProperty.KeyframesEnabled) + if (LayerProperty.KeyframesEnabled) { - var keyframes = LayerPropertyViewModel.LayerProperty.Keyframes.ToList(); - var toRemove = TimelineKeyframeViewModels.Where(t => !keyframes.Contains(t.BaseLayerPropertyKeyframe)).ToList(); + var keyframes = LayerProperty.Keyframes.ToList(); + var toRemove = Items.Where(t => !keyframes.Contains(t.LayerPropertyKeyframe)).ToList(); foreach (var timelineKeyframeViewModel in toRemove) timelineKeyframeViewModel.Dispose(); - TimelineKeyframeViewModels.RemoveRange(toRemove); - TimelineKeyframeViewModels.AddRange(keyframes - .Where(k => TimelineKeyframeViewModels.All(t => t.BaseLayerPropertyKeyframe != k)) - .Select(k => new TimelineKeyframeViewModel(_profileEditorService, k)) + Items.RemoveRange(toRemove); + Items.AddRange(keyframes + .Where(k => Items.All(t => t.LayerPropertyKeyframe != k)) + .Select(k => new TimelineKeyframeViewModel(k, _profileEditorService)) ); } else - DisposeKeyframeViewModels(); + Items.Clear(); - foreach (var timelineKeyframeViewModel in TimelineKeyframeViewModels) - timelineKeyframeViewModel.Update(_profileEditorService.PixelsPerSecond); + foreach (var timelineKeyframeViewModel in Items) + timelineKeyframeViewModel.Update(); } - public override void Dispose() + public void Dispose() { - _profileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged; - LayerPropertyViewModel.LayerProperty.KeyframeAdded -= LayerPropertyOnKeyframeModified; - LayerPropertyViewModel.LayerProperty.KeyframeRemoved -= LayerPropertyOnKeyframeModified; - LayerPropertyViewModel.LayerProperty.KeyframesToggled -= LayerPropertyOnKeyframeModified; - DisposeKeyframeViewModels(); - } - - private void DisposeKeyframeViewModels() - { - foreach (var timelineKeyframeViewModel in TimelineKeyframeViewModels) - timelineKeyframeViewModel.Dispose(); - TimelineKeyframeViewModels.Clear(); - } - - private void LayerPropertyOnKeyframeModified(object sender, EventArgs e) - { - UpdateKeyframes(); - } - - private void ProfileEditorServiceOnPixelsPerSecondChanged(object sender, EventArgs e) - { - foreach (var timelineKeyframeViewModel in TimelineKeyframeViewModels) - timelineKeyframeViewModel.Update(_profileEditorService.PixelsPerSecond); - - Width = TimelineKeyframeViewModels.Any() ? TimelineKeyframeViewModels.Max(t => t.X) + 25 : 0; } } - public abstract class TimelinePropertyViewModel : PropertyChangedBase, IDisposable + public interface ITimelinePropertyViewModel : IScreen, IDisposable { - private BindableCollection _timelineKeyframeViewModels; - private double _width; - - protected TimelinePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel) - { - LayerPropertyBaseViewModel = layerPropertyBaseViewModel; - TimelineKeyframeViewModels = new BindableCollection(); - } - - public LayerPropertyBaseViewModel LayerPropertyBaseViewModel { get; } - - public BindableCollection TimelineKeyframeViewModels - { - get => _timelineKeyframeViewModels; - set => SetAndNotify(ref _timelineKeyframeViewModels, value); - } - - public double Width - { - get => _width; - set => SetAndNotify(ref _width, value); - } - - public abstract void Dispose(); - - public abstract void UpdateKeyframes(); + List GetAllKeyframePositions(); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs index 133f0289f..263167481 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs @@ -8,7 +8,6 @@ using System.Windows.Input; using System.Windows.Media; using System.Windows.Shapes; using Artemis.Core; -using Artemis.UI.Screens.ProfileEditor.LayerProperties.Abstract; using Artemis.UI.Shared; using Artemis.UI.Shared.Services; using Stylet; @@ -32,8 +31,6 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline SelectedProfileElement.PropertyChanged += SelectedProfileElementOnPropertyChanged; _profileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged; - - Update(); } public RenderProfileElement SelectedProfileElement { get; set; } @@ -62,21 +59,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline _profileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged; SelectedProfileElement.PropertyChanged -= SelectedProfileElementOnPropertyChanged; } - - public void Update() - { - foreach (var layerPropertyGroupViewModel in LayerPropertyGroups) - { - layerPropertyGroupViewModel.TimelinePropertyGroupViewModel.UpdateKeyframes(); - - foreach (var layerPropertyBaseViewModel in layerPropertyGroupViewModel.GetAllChildren()) - { - if (layerPropertyBaseViewModel is LayerPropertyViewModel layerPropertyViewModel) - layerPropertyViewModel.TimelinePropertyBaseViewModel.UpdateKeyframes(); - } - } - } - + private void SelectedProfileElementOnPropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(_profileEditorService.SelectedProfileElement.StartSegmentLength)) diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/LayerPropertyGroupTreeView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeGroupView.xaml similarity index 98% rename from src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/LayerPropertyGroupTreeView.xaml rename to src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeGroupView.xaml index a33638fbd..a8bcb0f63 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/LayerPropertyGroupTreeView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeGroupView.xaml @@ -1,4 +1,4 @@ - diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/LayerPropertyGroupTreeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeGroupViewModel.cs similarity index 98% rename from src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/LayerPropertyGroupTreeViewModel.cs rename to src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeGroupViewModel.cs index 1b4be1dfd..57f27bd6e 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/LayerPropertyGroupTreeViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeGroupViewModel.cs @@ -14,14 +14,14 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree { - public class LayerPropertyGroupTreeViewModel : PropertyChangedBase + public class TreeGroupViewModel : PropertyChangedBase { private readonly IDialogService _dialogService; private readonly IKernel _kernel; private readonly IProfileEditorService _profileEditorService; private readonly IWindowManager _windowManager; - public LayerPropertyGroupTreeViewModel( + public TreeGroupViewModel( LayerPropertyGroupViewModel layerPropertyGroupViewModel, IProfileEditorService profileEditorService, IDialogService dialogService, diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/LayerPropertyTreeView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreePropertyView.xaml similarity index 98% rename from src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/LayerPropertyTreeView.xaml rename to src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreePropertyView.xaml index f31147e5d..ff231f5e0 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/LayerPropertyTreeView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreePropertyView.xaml @@ -1,4 +1,4 @@ - : Screen, ILayerPropertyTreeViewModel + public class TreePropertyViewModel : Screen, ITreePropertyViewModel { private readonly IProfileEditorService _profileEditorService; private PropertyInputViewModel _propertyInputViewModel; - public LayerPropertyTreeViewModel(LayerProperty layerProperty, LayerPropertyViewModel layerPropertyViewModel, IProfileEditorService profileEditorService) + public TreePropertyViewModel(LayerProperty layerProperty, LayerPropertyViewModel layerPropertyViewModel, IProfileEditorService profileEditorService) { _profileEditorService = profileEditorService; LayerProperty = layerProperty; @@ -86,7 +86,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree #endregion } - public interface ILayerPropertyTreeViewModel : IScreen, IDisposable + public interface ITreePropertyViewModel : IScreen, IDisposable { bool HasPropertyInputViewModel { get; } } diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeView.xaml index 49e945658..66ec6e3ac 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeView.xaml @@ -99,10 +99,10 @@ - + - + From db9d9fb4e6226fde4ae1e182e9cca6d4c8c7da8d Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Fri, 11 Sep 2020 22:30:57 +0200 Subject: [PATCH 09/12] Profile editor - Reimplemented more of the timeline --- src/Artemis.Core/Models/Profile/Layer.cs | 10 ++- .../Profile/LayerProperties/LayerProperty.cs | 22 +++---- .../Input/DataModelDynamicViewModel.cs | 5 +- .../Services/DataModelUIService.cs | 26 +++++--- .../Interfaces/IDataModelUIService.cs | 9 +-- .../Ninject/Factories/IVMFactory.cs | 10 +-- .../BrushPropertyInputViewModel.cs | 14 ++--- .../DisplayConditionListPredicateViewModel.cs | 5 +- .../DisplayConditionListViewModel.cs | 5 +- .../DisplayConditionPredicateViewModel.cs | 11 +--- .../DataBindingModifierViewModel.cs | 46 +++++++------- .../DataBindings/DataBindingViewModel.cs | 40 +++++++----- .../DataBindings/DataBindingsTabsView.xaml | 16 ----- .../DataBindings/DataBindingsTabsViewModel.cs | 14 ----- .../DataBindings/DataBindingsView.xaml | 16 ++--- .../DataBindings/DataBindingsViewModel.cs | 45 +++----------- .../LayerPropertiesViewModel.cs | 52 ++++++++-------- .../LayerPropertyGroupViewModel.cs | 41 ++++++++++-- .../Timeline/TimelineGroupViewModel.cs | 4 +- .../Timeline/TimelineKeyframeViewModel.cs | 62 +++++++++++++++---- .../Timeline/TimelinePropertyViewModel.cs | 32 +++++++++- .../Timeline/TimelineSegmentViewModel.cs | 36 +++++++---- .../Timeline/TimelineViewModel.cs | 59 +++++------------- .../Tools/SelectionToolViewModel.cs | 10 +-- 24 files changed, 294 insertions(+), 296 deletions(-) delete mode 100644 src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsTabsView.xaml delete mode 100644 src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsTabsViewModel.cs diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index fc8dbb7c6..1cd1e5445 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics; using System.Linq; using Artemis.Core.LayerBrushes; using Artemis.Core.LayerEffects; @@ -672,6 +673,13 @@ namespace Artemis.Core if (descriptor == null) throw new ArgumentNullException(nameof(descriptor)); + if (LayerBrush != null) + { + var brush = LayerBrush; + LayerBrush = null; + brush.Dispose(); + } + // Ensure the brush reference matches the brush var current = General.BrushReference.CurrentValue; if (current.BrushPluginGuid != descriptor.LayerBrushProvider.PluginInfo.Guid || current.BrushType != descriptor.LayerBrushType.Name) @@ -721,7 +729,7 @@ namespace Artemis.Core } #endregion - + #region Event handlers private void LayerBrushStoreOnLayerBrushRemoved(object sender, LayerBrushStoreEvent e) diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs index ed49633ad..339401608 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs @@ -19,7 +19,7 @@ namespace Artemis.Core public abstract class LayerProperty : ILayerProperty { private bool _disposed; - + /// /// Creates a new instance of the class /// @@ -274,19 +274,6 @@ namespace Artemis.Core OnKeyframeRemoved(); } - /// - /// Removes all keyframes from the layer property - /// - public void ClearKeyframes() - { - if (_disposed) - throw new ObjectDisposedException("LayerProperty"); - - var keyframes = new List>(_keyframes); - foreach (var layerPropertyKeyframe in keyframes) - RemoveKeyframe(layerPropertyKeyframe); - } - /// /// Sorts the keyframes in ascending order by position /// @@ -343,6 +330,11 @@ namespace Artemis.Core return (DataBindingRegistration) match; } + public List GetAllDataBindingRegistrations() + { + return _dataBindingRegistrations; + } + public void RegisterDataBindingProperty(Expression> propertyLambda, DataBindingConverter converter) { if (_disposed) @@ -593,7 +585,7 @@ namespace Artemis.Core { _disposed = true; - foreach (var dataBinding in _dataBindings) + foreach (var dataBinding in _dataBindings) dataBinding.Dispose(); } } diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicViewModel.cs index bc718153d..2e97c6bbb 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicViewModel.cs @@ -89,10 +89,7 @@ namespace Artemis.UI.Shared.Input private void Initialize() { // Get the data models - DataModelViewModel = _dataModelUIService.GetMainDataModelVisualization(); - if (!_dataModelUIService.GetPluginExtendsDataModel(_module)) - DataModelViewModel.Children.Add(_dataModelUIService.GetPluginDataModelVisualization(_module)); - + DataModelViewModel = _dataModelUIService.GetPluginDataModelVisualization(_module, true); DataModelViewModel.UpdateRequested += DataModelOnUpdateRequested; _updateTimer.Start(); diff --git a/src/Artemis.UI.Shared/Services/DataModelUIService.cs b/src/Artemis.UI.Shared/Services/DataModelUIService.cs index 73e34e9e6..280bc4b5f 100644 --- a/src/Artemis.UI.Shared/Services/DataModelUIService.cs +++ b/src/Artemis.UI.Shared/Services/DataModelUIService.cs @@ -32,7 +32,7 @@ namespace Artemis.UI.Shared.Services public DataModelPropertiesViewModel GetMainDataModelVisualization() { var viewModel = new DataModelPropertiesViewModel(null, null, null); - foreach (var dataModelExpansion in _dataModelService.DataModelExpansions) + foreach (var dataModelExpansion in _dataModelService.GetDataModels()) viewModel.Children.Add(new DataModelPropertiesViewModel(dataModelExpansion, viewModel, null)); // Update to populate children @@ -41,8 +41,23 @@ namespace Artemis.UI.Shared.Services return viewModel; } - public DataModelPropertiesViewModel GetPluginDataModelVisualization(Plugin plugin) + public DataModelPropertiesViewModel GetPluginDataModelVisualization(Plugin plugin, bool includeMainDataModel) { + if (includeMainDataModel) + { + var mainDataModel = GetMainDataModelVisualization(); + + // If the main data model already includes the plugin data model we're done + if (mainDataModel.Children.Any(c => c.DataModel.PluginInfo.Instance == plugin)) + return mainDataModel; + // Otherwise get just the plugin data model and add it + var pluginDataModel = GetPluginDataModelVisualization(plugin, false); + if (pluginDataModel != null) + mainDataModel.Children.Add(pluginDataModel); + + return mainDataModel; + } + var dataModel = _dataModelService.GetPluginDataModel(plugin); if (dataModel == null) return null; @@ -55,12 +70,7 @@ namespace Artemis.UI.Shared.Services viewModel.UpdateRequested += (sender, args) => viewModel.Update(this); return viewModel; } - - public bool GetPluginExtendsDataModel(Plugin plugin) - { - return _dataModelService.GetPluginExtendsDataModel(plugin); - } - + public DataModelVisualizationRegistration RegisterDataModelInput(PluginInfo pluginInfo, IReadOnlyCollection compatibleConversionTypes = null) where T : DataModelInputViewModel { if (compatibleConversionTypes == null) diff --git a/src/Artemis.UI.Shared/Services/Interfaces/IDataModelUIService.cs b/src/Artemis.UI.Shared/Services/Interfaces/IDataModelUIService.cs index 049a1bf11..f9cb711df 100644 --- a/src/Artemis.UI.Shared/Services/Interfaces/IDataModelUIService.cs +++ b/src/Artemis.UI.Shared/Services/Interfaces/IDataModelUIService.cs @@ -12,14 +12,7 @@ namespace Artemis.UI.Shared.Services IReadOnlyCollection RegisteredDataModelEditors { get; } IReadOnlyCollection RegisteredDataModelDisplays { get; } DataModelPropertiesViewModel GetMainDataModelVisualization(); - DataModelPropertiesViewModel GetPluginDataModelVisualization(Plugin plugin); - - /// - /// Determines whether the given plugin expands the main data model - /// - /// - /// - bool GetPluginExtendsDataModel(Plugin plugin); + DataModelPropertiesViewModel GetPluginDataModelVisualization(Plugin plugin, bool includeMainDataModel); DataModelVisualizationRegistration RegisterDataModelInput(PluginInfo pluginInfo, IReadOnlyCollection compatibleConversionTypes) where T : DataModelInputViewModel; DataModelVisualizationRegistration RegisterDataModelDisplay(PluginInfo pluginInfo) where T : DataModelDisplayViewModel; diff --git a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs index 86b82e29e..ef45ad046 100644 --- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -71,21 +71,21 @@ namespace Artemis.UI.Ninject.Factories public interface IDataBindingsVmFactory : IVmFactory { - DataBindingsViewModel DataBindingsViewModel(BaseLayerProperty layerProperty); - DataBindingViewModel DataBindingViewModel(DataBindingRegistration registration); - DataBindingModifierViewModel DataBindingModifierViewModel(DataBindingModifier modifier); + IDataBindingViewModel DataBindingViewModel(IDataBindingRegistration registration); + DataBindingModifierViewModel DataBindingModifierViewModel(DataBindingModifier modifier); } public interface ILayerPropertyVmFactory : IVmFactory { LayerPropertyViewModel LayerPropertyViewModel(ILayerProperty layerProperty); + LayerPropertyGroupViewModel LayerPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup); - TreePropertyViewModel LayerPropertyGroupViewModel(LayerProperty layerProperty); TreeGroupViewModel TreeGroupViewModel(LayerPropertyGroupViewModel layerPropertyGroupViewModel); TimelineGroupViewModel TimelineGroupViewModel(LayerPropertyGroupViewModel layerPropertyGroupViewModel); - LayerPropertyGroupViewModel LayerPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, PropertyGroupDescriptionAttribute propertyGroupDescription); + TreeViewModel TreeViewModel(LayerPropertiesViewModel layerPropertiesViewModel, BindableCollection layerPropertyGroups); EffectsViewModel EffectsViewModel(LayerPropertiesViewModel layerPropertiesViewModel); TimelineViewModel TimelineViewModel(LayerPropertiesViewModel layerPropertiesViewModel, BindableCollection layerPropertyGroups); + TimelineSegmentViewModel TimelineSegmentViewModel(SegmentViewModelType segment, BindableCollection layerPropertyGroups); } } \ No newline at end of file diff --git a/src/Artemis.UI/PropertyInput/BrushPropertyInputViewModel.cs b/src/Artemis.UI/PropertyInput/BrushPropertyInputViewModel.cs index a51d405fb..2222ecf81 100644 --- a/src/Artemis.UI/PropertyInput/BrushPropertyInputViewModel.cs +++ b/src/Artemis.UI/PropertyInput/BrushPropertyInputViewModel.cs @@ -11,13 +11,12 @@ namespace Artemis.UI.PropertyInput public class BrushPropertyInputViewModel : PropertyInputViewModel { private readonly IPluginService _pluginService; - private readonly IRenderElementService _renderElementService; private List _descriptors; - public BrushPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, - IRenderElementService renderElementService, IPluginService pluginService) : base(layerProperty, profileEditorService) + public BrushPropertyInputViewModel(LayerProperty layerProperty, + IProfileEditorService profileEditorService, + IPluginService pluginService) : base(layerProperty, profileEditorService) { - _renderElementService = renderElementService; _pluginService = pluginService; _pluginService.PluginEnabled += PluginServiceOnPluginLoaded; @@ -54,11 +53,8 @@ namespace Artemis.UI.PropertyInput protected override void OnInputValueApplied() { - if (LayerProperty.ProfileElement is Layer layer) - { - _renderElementService.RemoveLayerBrush(layer); - _renderElementService.InstantiateLayerBrush(layer); - } + if (LayerProperty.ProfileElement is Layer layer) + layer.ChangeLayerBrush(SelectedDescriptor); } private void SetBrushByDescriptor(LayerBrushDescriptor value) diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListPredicateViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListPredicateViewModel.cs index 7d07d3c11..07738d392 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListPredicateViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListPredicateViewModel.cs @@ -308,10 +308,7 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions if (DisplayConditionListPredicate.ListDataModel == null || DisplayConditionListPredicate.ListPropertyPath == null) throw new ArtemisUIException("Cannot create a list predicate without first selecting a target list"); - var dataModel = _dataModelUIService.GetMainDataModelVisualization(); - if (!_dataModelUIService.GetPluginExtendsDataModel(_profileEditorService.GetCurrentModule())) - dataModel.Children.Add(_dataModelUIService.GetPluginDataModelVisualization(_profileEditorService.GetCurrentModule())); - + var dataModel = _dataModelUIService.GetPluginDataModelVisualization(_profileEditorService.GetCurrentModule(), true); var listDataModel = (DataModelListViewModel) dataModel.GetChildByPath( DisplayConditionListPredicate.ListDataModel.PluginInfo.Guid, DisplayConditionListPredicate.ListPropertyPath diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListViewModel.cs index 73393f6cb..f627d87bc 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListViewModel.cs @@ -109,10 +109,7 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions public void Initialize() { // Get the data models - TargetDataModel = _dataModelUIService.GetMainDataModelVisualization(); - if (!_dataModelUIService.GetPluginExtendsDataModel(_profileEditorService.GetCurrentModule())) - TargetDataModel.Children.Add(_dataModelUIService.GetPluginDataModelVisualization(_profileEditorService.GetCurrentModule())); - + TargetDataModel = _dataModelUIService.GetPluginDataModelVisualization(_profileEditorService.GetCurrentModule(), true); TargetDataModel.UpdateRequested += TargetDataModelUpdateRequested; Update(); diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs index f8b7833bd..645237a34 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs @@ -168,14 +168,9 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions public void Initialize() { // Get the data models - LeftSideDataModel = _dataModelUIService.GetMainDataModelVisualization(); - RightSideDataModel = _dataModelUIService.GetMainDataModelVisualization(); - if (!_dataModelUIService.GetPluginExtendsDataModel(_profileEditorService.GetCurrentModule())) - { - LeftSideDataModel.Children.Add(_dataModelUIService.GetPluginDataModelVisualization(_profileEditorService.GetCurrentModule())); - RightSideDataModel.Children.Add(_dataModelUIService.GetPluginDataModelVisualization(_profileEditorService.GetCurrentModule())); - } - + LeftSideDataModel = _dataModelUIService.GetPluginDataModelVisualization(_profileEditorService.GetCurrentModule(), true); + RightSideDataModel = _dataModelUIService.GetPluginDataModelVisualization(_profileEditorService.GetCurrentModule(), true); + // Determine which types are currently supported var editors = _dataModelUIService.RegisteredDataModelEditors; _supportedInputTypes = editors.Select(e => e.SupportedType).ToList(); diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingModifierViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingModifierViewModel.cs index 58849824e..4ce493458 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingModifierViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingModifierViewModel.cs @@ -1,6 +1,4 @@ -using System; -using System.Threading.Tasks; -using Artemis.Core; +using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Exceptions; using Artemis.UI.Shared; @@ -10,16 +8,16 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings { - public class DataBindingModifierViewModel : PropertyChangedBase + public class DataBindingModifierViewModel : PropertyChangedBase { private readonly IDataBindingService _dataBindingService; private readonly IDataModelUIService _dataModelUIService; private readonly IProfileEditorService _profileEditorService; - private DataBindingModifierType _selectedModifierType; private DataModelDynamicViewModel _dynamicSelectionViewModel; + private DataBindingModifierType _selectedModifierType; private DataModelStaticViewModel _staticInputViewModel; - public DataBindingModifierViewModel(DataBindingModifier modifier, + public DataBindingModifierViewModel(DataBindingModifier modifier, IDataBindingService dataBindingService, ISettingsService settingsService, IDataModelUIService dataModelUIService, @@ -42,7 +40,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings public DelegateCommand SelectModifierTypeCommand { get; } public PluginSetting ShowDataModelValues { get; } - public DataBindingModifier Modifier { get; } + public DataBindingModifier Modifier { get; } public BindableCollection ModifierTypes { get; } public DataBindingModifierType SelectedModifierType @@ -62,7 +60,22 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings get => _staticInputViewModel; private set => SetAndNotify(ref _staticInputViewModel, value); } - + + public void Delete() + { + Modifier.DataBinding.RemoveModifier(Modifier); + } + + public void SwapType() + { + if (Modifier.ParameterType == ProfileRightSideType.Dynamic) + Modifier.UpdateParameter(Modifier.DataBinding.GetSourceType().GetDefault()); + else + Modifier.UpdateParameter(null, null); + + Update(); + } + private void ParameterSelectionViewModelOnPropertySelected(object sender, DataModelInputDynamicEventArgs e) { Modifier.UpdateParameter(e.DataModelVisualizationViewModel.DataModel, e.DataModelVisualizationViewModel.PropertyPath); @@ -89,7 +102,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings StaticInputViewModel = null; DynamicSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_profileEditorService.GetCurrentModule()); DynamicSelectionViewModel.PropertySelected += ParameterSelectionViewModelOnPropertySelected; - DynamicSelectionViewModel.FilterTypes = new[] { sourceType }; + DynamicSelectionViewModel.FilterTypes = new[] {sourceType}; } else { @@ -120,20 +133,5 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings Update(); } - - public void Delete() - { - Modifier.DataBinding.RemoveModifier(Modifier); - } - - public void SwapType() - { - if (Modifier.ParameterType == ProfileRightSideType.Dynamic) - Modifier.UpdateParameter(Modifier.DataBinding.GetSourceType().GetDefault()); - else - Modifier.UpdateParameter(null, null); - - Update(); - } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs index 6397b9437..dc89d11a1 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs @@ -10,23 +10,23 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings { - public class DataBindingViewModel : PropertyChangedBase, IDisposable + public class DataBindingViewModel : Screen, IDataBindingViewModel { private readonly IDataBindingsVmFactory _dataBindingsVmFactory; private readonly IDataModelUIService _dataModelUIService; private readonly IProfileEditorService _profileEditorService; - private DataBinding _dataBinding; + private DataBinding _dataBinding; private int _easingTime; private bool _isDataBindingEnabled; private bool _isEasingTimeEnabled; private DataBindingMode _selectedDataBindingMode; private TimelineEasingViewModel _selectedEasingViewModel; private DataModelDynamicViewModel _targetSelectionViewModel; - private object _testInputValue; - private object _testResultValue; + private TProperty _testInputValue; + private TProperty _testResultValue; private bool _updating; - public DataBindingViewModel(DataBindingRegistration registration, + public DataBindingViewModel(DataBindingRegistration registration, IProfileEditorService profileEditorService, IDataModelUIService dataModelUIService, IDataBindingsVmFactory dataBindingsVmFactory) @@ -40,7 +40,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings DataBindingModes = new BindableCollection(EnumUtilities.GetAllValuesAndDescriptions(typeof(DataBindingMode))); EasingViewModels = new BindableCollection(); - ModifierViewModels = new BindableCollection(); + ModifierViewModels = new BindableCollection>(); DataBinding = Registration.DataBinding; if (DataBinding != null) @@ -52,12 +52,11 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings Execute.PostToUIThread(Initialize); } - public DataBindingRegistration Registration { get; } - public string DisplayName { get; } + public DataBindingRegistration Registration { get; } public BindableCollection DataBindingModes { get; } public BindableCollection EasingViewModels { get; } - public BindableCollection ModifierViewModels { get; } + public BindableCollection> ModifierViewModels { get; } public DataBindingMode SelectedDataBindingMode { @@ -114,7 +113,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings } } - public DataBinding DataBinding + public DataBinding DataBinding { get => _dataBinding; set @@ -124,13 +123,13 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings } } - public object TestInputValue + public TProperty TestInputValue { get => _testInputValue; set => SetAndNotify(ref _testInputValue, value); } - public object TestResultValue + public TProperty TestResultValue { get => _testResultValue; set => SetAndNotify(ref _testResultValue, value); @@ -167,7 +166,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings if (DataBinding == null) return; - var modifier = new DataBindingModifier(ProfileRightSideType.Dynamic); + var modifier = new DataBindingModifier(ProfileRightSideType.Dynamic); DataBinding.AddModifier(modifier); _profileEditorService.UpdateSelectedProfileElement(); @@ -175,7 +174,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings private void Initialize() { - EasingViewModels.AddRange(Enum.GetValues(typeof(Easings.Functions)).Cast().Select(v => new TimelineEasingViewModel(null, v))); + EasingViewModels.AddRange(Enum.GetValues(typeof(Easings.Functions)).Cast().Select(v => new TimelineEasingViewModel(v, false))); TargetSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_profileEditorService.GetCurrentModule()); TargetSelectionViewModel.PropertySelected += TargetSelectionViewModelOnPropertySelected; _profileEditorService.ProfilePreviewUpdated += ProfileEditorServiceOnProfilePreviewUpdated; @@ -229,9 +228,12 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings var currentValue = TargetSelectionViewModel.SelectedPropertyViewModel?.GetCurrentValue(); if (currentValue == null && Registration.Property.PropertyType.IsValueType) currentValue = Activator.CreateInstance(Registration.Property.PropertyType); - - TestInputValue = Convert.ChangeType(currentValue, Registration.Property.PropertyType); - TestResultValue = DataBinding?.GetValue(TestInputValue); + + TestInputValue = currentValue is TProperty testInputValue ? testInputValue : default; + if (DataBinding != null) + TestResultValue = DataBinding.GetValue(TestInputValue); + else + TestInputValue = default; } private void UpdateModifierViewModels() @@ -268,4 +270,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings DataBinding.ModifiersUpdated -= DataBindingOnModifiersUpdated; } } + + public interface IDataBindingViewModel : IScreen, IDisposable + { + } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsTabsView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsTabsView.xaml deleted file mode 100644 index 371cdb7a6..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsTabsView.xaml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsTabsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsTabsViewModel.cs deleted file mode 100644 index 18355e690..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsTabsViewModel.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Stylet; - -namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings -{ - public class DataBindingsTabsViewModel : PropertyChangedBase - { - public DataBindingsTabsViewModel() - { - Tabs = new BindableCollection(); - } - - public BindableCollection Tabs { get; set; } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsView.xaml index 625d041b5..85def1ef5 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsView.xaml @@ -3,14 +3,14 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings" xmlns:s="https://github.com/canton7/Stylet" mc:Ignorable="d" - d:DesignHeight="450" d:DesignWidth="800" - d:DataContext="{d:DesignInstance local:DataBindingsViewModel}"> - - - - - + 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 585abf21f..bebf2d871 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs @@ -6,58 +6,27 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings { - public class DataBindingsViewModel : PropertyChangedBase, IDisposable + public class DataBindingsViewModel : Conductor.Collection.AllActive { private readonly IDataBindingsVmFactory _dataBindingsVmFactory; - private DataBindingsTabsViewModel _dataBindingsTabsViewModel; - private DataBindingViewModel _dataBindingViewModel; - - public DataBindingsViewModel(BaseLayerProperty layerProperty, IDataBindingsVmFactory dataBindingsVmFactory) + + public DataBindingsViewModel(LayerProperty layerProperty, IDataBindingsVmFactory dataBindingsVmFactory) { _dataBindingsVmFactory = dataBindingsVmFactory; LayerProperty = layerProperty; Initialise(); } - public BaseLayerProperty LayerProperty { get; } - - public DataBindingViewModel DataBindingViewModel - { - get => _dataBindingViewModel; - set => SetAndNotify(ref _dataBindingViewModel, value); - } - - public DataBindingsTabsViewModel DataBindingsTabsViewModel - { - get => _dataBindingsTabsViewModel; - set => SetAndNotify(ref _dataBindingsTabsViewModel, value); - } + public LayerProperty LayerProperty { get; } private void Initialise() { - DataBindingViewModel?.Dispose(); - DataBindingViewModel = null; - DataBindingsTabsViewModel = null; - - var registrations = LayerProperty.DataBindingRegistrations; - if (registrations == null || registrations.Count == 0) - return; + var registrations = LayerProperty.GetAllDataBindingRegistrations(); // Create a data binding VM for each data bindable property. These VMs will be responsible for retrieving // and creating the actual data bindings - if (registrations.Count == 1) - DataBindingViewModel = _dataBindingsVmFactory.DataBindingViewModel(registrations.First()); - else - { - DataBindingsTabsViewModel = new DataBindingsTabsViewModel(); - foreach (var registration in registrations) - DataBindingsTabsViewModel.Tabs.Add(_dataBindingsVmFactory.DataBindingViewModel(registration)); - } - } - - public void Dispose() - { - DataBindingViewModel?.Dispose(); + foreach (var registration in registrations) + ActivateItem(_dataBindingsVmFactory.DataBindingViewModel(registration)); } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs index e3a6c0974..0e5311a24 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs @@ -289,28 +289,26 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties SelectedLayer.LayerBrushUpdated += SelectedLayerOnLayerBrushUpdated; // Add the built-in root groups of the layer - var generalAttribute = Attribute.GetCustomAttribute( - SelectedLayer.GetType().GetProperty(nameof(SelectedLayer.General)), - typeof(PropertyGroupDescriptionAttribute) - ); - var transformAttribute = Attribute.GetCustomAttribute( - SelectedLayer.GetType().GetProperty(nameof(SelectedLayer.Transform)), - typeof(PropertyGroupDescriptionAttribute) - ); - LayerPropertyGroups.Add(_layerPropertyVmFactory.LayerPropertyGroupViewModel(SelectedLayer.General, (PropertyGroupDescriptionAttribute) generalAttribute)); - LayerPropertyGroups.Add(_layerPropertyVmFactory.LayerPropertyGroupViewModel(SelectedLayer.Transform, (PropertyGroupDescriptionAttribute) transformAttribute)); + LayerPropertyGroups.Add(_layerPropertyVmFactory.LayerPropertyGroupViewModel(SelectedLayer.General)); + LayerPropertyGroups.Add(_layerPropertyVmFactory.LayerPropertyGroupViewModel(SelectedLayer.Transform)); } TreeViewModel = _layerPropertyVmFactory.TreeViewModel(this, LayerPropertyGroups); - TimelineViewModel?.Dispose(); + DeactivateItem(TimelineViewModel); + DeactivateItem(StartTimelineSegmentViewModel); + DeactivateItem(MainTimelineSegmentViewModel); + DeactivateItem(EndTimelineSegmentViewModel); + TimelineViewModel = _layerPropertyVmFactory.TimelineViewModel(this, LayerPropertyGroups); + ActivateItem(TimelineViewModel); + StartTimelineSegmentViewModel?.Dispose(); - StartTimelineSegmentViewModel = new TimelineSegmentViewModel(ProfileEditorService, SegmentViewModelType.Start); + StartTimelineSegmentViewModel = _layerPropertyVmFactory.TimelineSegmentViewModel(SegmentViewModelType.Start, LayerPropertyGroups); MainTimelineSegmentViewModel?.Dispose(); - MainTimelineSegmentViewModel = new TimelineSegmentViewModel(ProfileEditorService, SegmentViewModelType.Main); + MainTimelineSegmentViewModel = _layerPropertyVmFactory.TimelineSegmentViewModel(SegmentViewModelType.Main, LayerPropertyGroups); EndTimelineSegmentViewModel?.Dispose(); - EndTimelineSegmentViewModel = new TimelineSegmentViewModel(ProfileEditorService, SegmentViewModelType.End); + EndTimelineSegmentViewModel = _layerPropertyVmFactory.TimelineSegmentViewModel(SegmentViewModelType.End, LayerPropertyGroups); ApplyLayerBrush(); ApplyEffects(); @@ -346,19 +344,19 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties if (SelectedLayer.LayerBrush != null) { - // Add the rout group of the brush + // TODO: wat? + // Add the root group of the brush // The root group of the brush has no attribute so let's pull one out of our sleeve var brushDescription = new PropertyGroupDescriptionAttribute { Name = SelectedLayer.LayerBrush.Descriptor.DisplayName, Description = SelectedLayer.LayerBrush.Descriptor.Description }; - _brushPropertyGroup = _layerPropertyVmFactory.LayerPropertyGroupViewModel(SelectedLayer.LayerBrush.BaseProperties, brushDescription); + _brushPropertyGroup = _layerPropertyVmFactory.LayerPropertyGroupViewModel(SelectedLayer.LayerBrush.BaseProperties); LayerPropertyGroups.Add(_brushPropertyGroup); } SortProperties(); - TimelineViewModel.Update(); } private void ApplyEffects() @@ -382,18 +380,18 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties if (LayerPropertyGroups.Any(l => l.LayerPropertyGroup.LayerEffect == layerEffect)) continue; - // Add the rout group of the brush + // TODO: wat? + // Add the root group of the brush // The root group of the brush has no attribute so let's pull one out of our sleeve var brushDescription = new PropertyGroupDescriptionAttribute { Name = layerEffect.Descriptor.DisplayName, Description = layerEffect.Descriptor.Description }; - LayerPropertyGroups.Add(_layerPropertyVmFactory.LayerPropertyGroupViewModel(layerEffect.BaseProperties, brushDescription)); + LayerPropertyGroups.Add(_layerPropertyVmFactory.LayerPropertyGroupViewModel(layerEffect.BaseProperties)); } SortProperties(); - TimelineViewModel.Update(); } private void SortProperties() @@ -422,7 +420,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties LayerPropertyGroups.Move(LayerPropertyGroups.IndexOf(layerPropertyGroupViewModel), index + nonEffectProperties.Count); } } - + #endregion #region Drag and drop @@ -437,8 +435,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties var source = dropInfo.Data as LayerPropertyGroupViewModel; var target = dropInfo.TargetItem as LayerPropertyGroupViewModel; - if (source == target || - target?.TreeGroupViewModel.GroupType != LayerEffectRoot || + if (source == target || + target?.TreeGroupViewModel.GroupType != LayerEffectRoot || source?.TreeGroupViewModel.GroupType != LayerEffectRoot) return; @@ -557,13 +555,13 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties private TimeSpan CalculateEndTime() { - var keyframeTimes = LayerPropertyGroups.SelectMany(g => g.GetAllKeyframePositions(false)).ToList(); + var keyframeViewModels = LayerPropertyGroups.SelectMany(g => g.GetAllKeyframeViewModels(false)).ToList(); // If there are no keyframes, don't stop at all - if (!keyframeTimes.Any()) + if (!keyframeViewModels.Any()) return TimeSpan.MaxValue; // If there are keyframes, stop after the last keyframe + 10 sec - return keyframeTimes.Max().Add(TimeSpan.FromSeconds(10)); + return keyframeViewModels.Max(k => k.Position).Add(TimeSpan.FromSeconds(10)); } private void CoreServiceOnFrameRendering(object sender, FrameRenderingEventArgs e) @@ -620,7 +618,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties // If holding down shift, snap to the closest segment or keyframe if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) { - var snapTimes = LayerPropertyGroups.SelectMany(g => g.GetAllKeyframePositions(true)).ToList(); + var snapTimes = LayerPropertyGroups.SelectMany(g => g.GetAllKeyframeViewModels(true)).Select(k => k.Position).ToList(); var snappedTime = ProfileEditorService.SnapToTimeline(newTime, TimeSpan.FromMilliseconds(1000f / ProfileEditorService.PixelsPerSecond * 5), true, false, snapTimes); ProfileEditorService.CurrentTime = snappedTime; return; diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs index 09f4e5132..67ced6517 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs @@ -82,21 +82,54 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties NotifyOfPropertyChange(nameof(IsExpanded)); } - public List GetAllKeyframePositions(bool expandedOnly) + public List GetAllKeyframeViewModels(bool expandedOnly) { - var result = new List(); + var result = new List(); if (expandedOnly == IsExpanded) return result; foreach (var child in Children) { if (child is LayerPropertyViewModel layerPropertyViewModel) - result.AddRange(layerPropertyViewModel.TimelinePropertyViewModel.GetAllKeyframePositions()); + result.AddRange(layerPropertyViewModel.TimelinePropertyViewModel.GetAllKeyframeViewModels()); else if (child is LayerPropertyGroupViewModel layerPropertyGroupViewModel) - result.AddRange(layerPropertyGroupViewModel.GetAllKeyframePositions(expandedOnly)); + result.AddRange(layerPropertyGroupViewModel.GetAllKeyframeViewModels(expandedOnly)); } return result; } + + /// + /// Removes the keyframes between the and position from this property group + /// + /// The position at which to start removing keyframes, if null this will start at the first keyframe + /// The position at which to start removing keyframes, if null this will end at the last keyframe + public virtual void WipeKeyframes(TimeSpan? start, TimeSpan? end) + { + foreach (var child in Children) + { + if (child is LayerPropertyViewModel layerPropertyViewModel) + layerPropertyViewModel.TimelinePropertyViewModel.WipeKeyframes(start, end); + else if (child is LayerPropertyGroupViewModel layerPropertyGroupViewModel) + layerPropertyGroupViewModel.WipeKeyframes(start, end); + } + } + + /// + /// Shifts the keyframes between the and position by the provided + /// + /// The position at which to start shifting keyframes, if null this will start at the first keyframe + /// The position at which to start shifting keyframes, if null this will end at the last keyframe + /// The amount to shift the keyframes for + public void ShiftKeyframes(TimeSpan? start, TimeSpan? end, TimeSpan amount) + { + foreach (var child in Children) + { + if (child is LayerPropertyViewModel layerPropertyViewModel) + layerPropertyViewModel.TimelinePropertyViewModel.ShiftKeyframes(start, end, amount); + else if (child is LayerPropertyGroupViewModel layerPropertyGroupViewModel) + layerPropertyGroupViewModel.ShiftKeyframes(start, end, amount); + } + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineGroupViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineGroupViewModel.cs index 9e40b1bb8..cafd20ac0 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineGroupViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineGroupViewModel.cs @@ -41,8 +41,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline { KeyframePositions.Clear(); KeyframePositions.AddRange(LayerPropertyGroupViewModel - .GetAllKeyframePositions(false) - .Select(p => p.TotalSeconds * _profileEditorService.PixelsPerSecond)); + .GetAllKeyframeViewModels(false) + .Select(p => p.Position.TotalSeconds * _profileEditorService.PixelsPerSecond)); } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs index c586ce613..03c1a0635 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs @@ -7,7 +7,7 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline { - public class TimelineKeyframeViewModel : Screen, IDisposable + public class TimelineKeyframeViewModel : Screen, ITimelineKeyframeViewModel { private readonly IProfileEditorService _profileEditorService; @@ -31,12 +31,6 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline set => SetAndNotify(ref _easingViewModels, value); } - public bool IsSelected - { - get => _isSelected; - set => SetAndNotify(ref _isSelected, value); - } - public double X { get => _x; @@ -49,6 +43,14 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline set => SetAndNotify(ref _timestamp, value); } + public bool IsSelected + { + get => _isSelected; + set => SetAndNotify(ref _isSelected, value); + } + + public TimeSpan Position => LayerPropertyKeyframe.Position; + public void Dispose() { LayerPropertyKeyframe.PropertyChanged -= LayerPropertyKeyframeOnPropertyChanged; @@ -110,9 +112,9 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline _offset = null; } - public void SaveOffsetToKeyframe(TimelineKeyframeViewModel keyframeViewModel) + public void SaveOffsetToKeyframe(ITimelineKeyframeViewModel source) { - if (keyframeViewModel == this) + if (source == this) { _offset = null; return; @@ -121,15 +123,15 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline if (_offset != null) return; - _offset = LayerPropertyKeyframe.Position - keyframeViewModel.LayerPropertyKeyframe.Position; + _offset = LayerPropertyKeyframe.Position - source.Position; } - public void ApplyOffsetToKeyframe(TimelineKeyframeViewModel keyframeViewModel) + public void ApplyOffsetToKeyframe(ITimelineKeyframeViewModel source) { - if (keyframeViewModel == this || _offset == null) + if (source == this || _offset == null) return; - UpdatePosition(keyframeViewModel.LayerPropertyKeyframe.Position + _offset.Value); + UpdatePosition(source.Position + _offset.Value); } public void UpdatePosition(TimeSpan position) @@ -148,6 +150,18 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline #region Context menu actions + public void ContextMenuOpening() + { + CreateEasingViewModels(); + } + + public void ContextMenuClosing() + { + foreach (var timelineEasingViewModel in EasingViewModels) + timelineEasingViewModel.EasingModeSelected -= TimelineEasingViewModelOnEasingModeSelected; + EasingViewModels.Clear(); + } + public void Copy() { var newKeyframe = new LayerPropertyKeyframe( @@ -180,4 +194,26 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline #endregion } + + public interface ITimelineKeyframeViewModel : IScreen, IDisposable + { + bool IsSelected { get; set; } + TimeSpan Position { get; } + + #region Movement + + void SaveOffsetToKeyframe(ITimelineKeyframeViewModel source); + void ApplyOffsetToKeyframe(ITimelineKeyframeViewModel source); + void UpdatePosition(TimeSpan position); + void ReleaseMovement(); + + #endregion + + #region Context menu actions + + void Copy(); + void Delete(); + + #endregion + } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs index 320bf9099..c4426894a 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs @@ -21,9 +21,9 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline LayerPropertyViewModel = layerPropertyViewModel; } - public List GetAllKeyframePositions() + public List GetAllKeyframeViewModels() { - return LayerProperty.Keyframes.Select(k => k.Position).ToList(); + return Items.Cast().ToList(); } private void UpdateKeyframes() @@ -49,6 +49,30 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline timelineKeyframeViewModel.Update(); } + public void WipeKeyframes(TimeSpan? start, TimeSpan? end) + { + start ??= TimeSpan.Zero; + end ??= TimeSpan.MaxValue; + + var toShift = LayerProperty.Keyframes.Where(k => k.Position >= start && k.Position <= end).ToList(); + foreach (var keyframe in toShift) + LayerProperty.RemoveKeyframe(keyframe); + + UpdateKeyframes(); + } + + public void ShiftKeyframes(TimeSpan? start, TimeSpan? end, TimeSpan amount) + { + start ??= TimeSpan.Zero; + end ??= TimeSpan.MaxValue; + + var toShift = LayerProperty.Keyframes.Where(k => k.Position >= start && k.Position <= end).ToList(); + foreach (var keyframe in toShift) + keyframe.Position += amount; + + UpdateKeyframes(); + } + public void Dispose() { } @@ -56,6 +80,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline public interface ITimelinePropertyViewModel : IScreen, IDisposable { - List GetAllKeyframePositions(); + List GetAllKeyframeViewModels(); + void WipeKeyframes(TimeSpan? start, TimeSpan? end); + void ShiftKeyframes(TimeSpan? start, TimeSpan? end, TimeSpan amount); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs index 76a9c0c19..5404a6322 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs @@ -18,10 +18,12 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline private bool _showRepeatButton; private bool _showSegmentName; - public TimelineSegmentViewModel(IProfileEditorService profileEditorService, SegmentViewModelType segment) + public TimelineSegmentViewModel(SegmentViewModelType segment, BindableCollection layerPropertyGroups, + IProfileEditorService profileEditorService) { ProfileEditorService = profileEditorService; Segment = segment; + LayerPropertyGroups = layerPropertyGroups; SelectedProfileElement = ProfileEditorService.SelectedProfileElement; switch (Segment) @@ -47,6 +49,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline public RenderProfileElement SelectedProfileElement { get; } public SegmentViewModelType Segment { get; } + public BindableCollection LayerPropertyGroups { get; } public IProfileEditorService ProfileEditorService { get; } public string ToolTip { get; } @@ -130,7 +133,6 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline public void DisableSegment() { - var keyframes = SelectedProfileElement.GetAllKeyframes(); var startSegmentEnd = SelectedProfileElement.StartSegmentLength; var mainSegmentEnd = SelectedProfileElement.StartSegmentLength + SelectedProfileElement.MainSegmentLength; @@ -139,22 +141,19 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline if (Segment == SegmentViewModelType.Start) { // Remove keyframes that fall in this segment - foreach (var baseLayerPropertyKeyframe in keyframes.Where(k => k.Position <= startSegmentEnd)) - baseLayerPropertyKeyframe.Remove(); + WipeKeyframes(null, startSegmentEnd); SelectedProfileElement.StartSegmentLength = TimeSpan.Zero; } else if (Segment == SegmentViewModelType.Main) { // Remove keyframes that fall in this segment - foreach (var baseLayerPropertyKeyframe in keyframes.Where(k => k.Position > startSegmentEnd && k.Position <= mainSegmentEnd)) - baseLayerPropertyKeyframe.Remove(); + WipeKeyframes(startSegmentEnd, startSegmentEnd); SelectedProfileElement.MainSegmentLength = TimeSpan.Zero; } else if (Segment == SegmentViewModelType.End) { // Remove keyframes that fall in this segment - foreach (var baseLayerPropertyKeyframe in keyframes.Where(k => k.Position > mainSegmentEnd)) - baseLayerPropertyKeyframe.Remove(); + WipeKeyframes(mainSegmentEnd, null); SelectedProfileElement.EndSegmentLength = TimeSpan.Zero; } @@ -188,8 +187,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline else if (Segment == SegmentViewModelType.End) segmentEnd = SelectedProfileElement.TimelineLength; - foreach (var baseLayerPropertyKeyframe in SelectedProfileElement.GetAllKeyframes().Where(k => k.Position > segmentEnd)) - baseLayerPropertyKeyframe.Position += amount; + ShiftKeyframes(segmentEnd, null, amount); } public void SegmentMouseDown(object sender, MouseButtonEventArgs e) @@ -227,7 +225,10 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline // If holding down shift, snap to the closest element on the timeline if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) - newTime = ProfileEditorService.SnapToTimeline(newTime, TimeSpan.FromMilliseconds(1000f / ProfileEditorService.PixelsPerSecond * 5), false, true, true); + { + var keyframeTimes = LayerPropertyGroups.SelectMany(g => g.GetAllKeyframeViewModels(true)).Select(k => k.Position).ToList(); + newTime = ProfileEditorService.SnapToTimeline(newTime, TimeSpan.FromMilliseconds(1000f / ProfileEditorService.PixelsPerSecond * 5), false, true, keyframeTimes); + } // If holding down control, round to the closest 50ms else if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 50.0) * 50.0); @@ -272,7 +273,6 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline NotifyOfPropertyChange(nameof(RepeatSegment)); } - private void ProfileEditorServiceOnPixelsPerSecondChanged(object? sender, EventArgs e) { NotifyOfPropertyChange(nameof(SegmentWidth)); @@ -291,6 +291,18 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline ShowRepeatButton = SegmentWidth > 45 && IsMainSegment; ShowDisableButton = SegmentWidth > 25; } + + private void WipeKeyframes(TimeSpan? start, TimeSpan? end) + { + foreach (var layerPropertyGroupViewModel in LayerPropertyGroups) + layerPropertyGroupViewModel.WipeKeyframes(start, end); + } + + private void ShiftKeyframes(TimeSpan? start, TimeSpan? end, TimeSpan amount) + { + foreach (var layerPropertyGroupViewModel in LayerPropertyGroups) + layerPropertyGroupViewModel.ShiftKeyframes(start, end, amount); + } } public enum SegmentViewModelType diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs index 263167481..711949901 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs @@ -14,7 +14,7 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline { - public class TimelineViewModel : PropertyChangedBase, IViewAware, IDisposable + public class TimelineViewModel : Screen, IDisposable { private readonly LayerPropertiesViewModel _layerPropertiesViewModel; private readonly IProfileEditorService _profileEditorService; @@ -105,7 +105,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline // if (e.LeftButton == MouseButtonState.Released) // return; - var viewModel = (sender as Ellipse)?.DataContext as TimelineKeyframeViewModel; + var viewModel = (sender as Ellipse)?.DataContext as ITimelineKeyframeViewModel; if (viewModel == null) return; @@ -130,7 +130,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline public void KeyframeMouseMove(object sender, MouseEventArgs e) { - var viewModel = (sender as Ellipse)?.DataContext as TimelineKeyframeViewModel; + var viewModel = (sender as Ellipse)?.DataContext as ITimelineKeyframeViewModel; if (viewModel == null) return; @@ -142,24 +142,12 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline #region Context menu actions - public void ContextMenuOpening(object sender, ContextMenuEventArgs e) - { - var viewModel = (sender as Ellipse)?.DataContext as TimelineKeyframeViewModel; - viewModel?.CreateEasingViewModels(); - } - - public void ContextMenuClosing(object sender, ContextMenuEventArgs e) - { - var viewModel = (sender as Ellipse)?.DataContext as TimelineKeyframeViewModel; - viewModel?.EasingViewModels.Clear(); - } - - public void Copy(TimelineKeyframeViewModel viewModel) + public void Copy(ITimelineKeyframeViewModel viewModel) { viewModel.Copy(); } - public void Delete(TimelineKeyframeViewModel viewModel) + public void Delete(ITimelineKeyframeViewModel viewModel) { viewModel.Delete(); } @@ -198,7 +186,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline #region Keyframe movement - public void MoveSelectedKeyframes(TimeSpan cursorTime, TimelineKeyframeViewModel sourceKeyframeViewModel) + public void MoveSelectedKeyframes(TimeSpan cursorTime, ITimelineKeyframeViewModel sourceKeyframeViewModel) { // Ensure the selection rectangle doesn't show, the view isn't aware of different types of dragging SelectionRectangle.Rect = new Rect(); @@ -214,8 +202,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline TimeSpan.FromMilliseconds(1000f / _profileEditorService.PixelsPerSecond * 5), true, false, - true, - sourceKeyframeViewModel.BaseLayerPropertyKeyframe + keyframeViewModels.Where(k => k != sourceKeyframeViewModel).Select(k => k.Position).ToList() ); } @@ -267,7 +254,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline SelectionRectangle.Rect = selectedRect; var keyframeViewModels = GetAllKeyframeViewModels(); - var selectedKeyframes = HitTestUtilities.GetHitViewModels((Visual) sender, SelectionRectangle); + var selectedKeyframes = HitTestUtilities.GetHitViewModels((Visual) sender, SelectionRectangle); foreach (var keyframeViewModel in keyframeViewModels) keyframeViewModel.IsSelected = selectedKeyframes.Contains(keyframeViewModel); @@ -287,7 +274,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline } } - public void SelectKeyframe(TimelineKeyframeViewModel clicked, bool selectBetween, bool toggle) + public void SelectKeyframe(ITimelineKeyframeViewModel clicked, bool selectBetween, bool toggle) { var keyframeViewModels = GetAllKeyframeViewModels(); if (selectBetween) @@ -329,33 +316,15 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline } } - private List GetAllKeyframeViewModels() + private List GetAllKeyframeViewModels() { - var viewModels = new List(); + var viewModels = new List(); foreach (var layerPropertyGroupViewModel in LayerPropertyGroups) - viewModels.AddRange(layerPropertyGroupViewModel.GetAllChildren()); - - var keyframes = viewModels.Where(vm => vm is LayerPropertyViewModel) - .SelectMany(vm => ((LayerPropertyViewModel) vm).TimelinePropertyBaseViewModel.TimelineKeyframeViewModels) - .ToList(); - - return keyframes; + viewModels.AddRange(layerPropertyGroupViewModel.GetAllKeyframeViewModels(false)); + + return viewModels; } #endregion - - #region IViewAware - - public void AttachView(UIElement view) - { - if (View != null) - throw new InvalidOperationException(string.Format("Tried to attach View {0} to ViewModel {1}, but it already has a view attached", view.GetType().Name, GetType().Name)); - - View = view; - } - - public UIElement View { get; set; } - - #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Visualization/Tools/SelectionToolViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Visualization/Tools/SelectionToolViewModel.cs index c6e84ce99..46d952de5 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Visualization/Tools/SelectionToolViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Visualization/Tools/SelectionToolViewModel.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Windows; using System.Windows.Input; using Artemis.Core; -using Artemis.Core.Services; using Artemis.UI.Properties; using Artemis.UI.Shared.Services; @@ -11,13 +10,10 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization.Tools { public class SelectionToolViewModel : VisualizationToolViewModel { - private readonly IRenderElementService _renderElementService; private Rect _dragRectangle; - public SelectionToolViewModel(ProfileViewModel profileViewModel, IProfileEditorService profileEditorService, IRenderElementService renderElementService) - : base(profileViewModel, profileEditorService) + public SelectionToolViewModel(ProfileViewModel profileViewModel, IProfileEditorService profileEditorService) : base(profileViewModel, profileEditorService) { - _renderElementService = renderElementService; using (var stream = new MemoryStream(Resources.aero_crosshair)) { Cursor = new Cursor(stream); @@ -57,7 +53,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization.Tools // If no layer selected, apply it to a new layer in the selected folder else if (ProfileEditorService.SelectedProfileElement is Folder folder) { - var newLayer = _renderElementService.CreateLayer(folder.Profile, folder, "New layer"); + var newLayer = new Layer(folder, "New layer"); newLayer.AddLeds(selectedLeds); ProfileEditorService.ChangeSelectedProfileElement(newLayer); ProfileEditorService.UpdateSelectedProfileElement(); @@ -66,7 +62,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization.Tools else { var rootFolder = ProfileEditorService.SelectedProfile.GetRootFolder(); - var newLayer = _renderElementService.CreateLayer(rootFolder.Profile, rootFolder, "New layer"); + var newLayer = new Layer(rootFolder, "New layer"); newLayer.AddLeds(selectedLeds); ProfileEditorService.ChangeSelectedProfileElement(newLayer); ProfileEditorService.UpdateSelectedProfileElement(); From 7fff1a593f0abb6b4cc7c7578447b7161c8ab288 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Sat, 12 Sep 2020 23:11:08 +0200 Subject: [PATCH 10/12] Profile editor - Many runtime fixes, UI is back to usable! --- pointers.json | 75 --------- src/Artemis.Core/Artemis.Core.csproj | 1 + .../Converters/FloatDataBindingConverter.cs | 4 +- .../Converters/GeneralDataBindingConverter.cs | 4 +- .../Converters/IntDataBindingConverter.cs | 8 +- .../DataBindings/DataBindingConverter.cs | 31 +--- .../DataBindings/DataBindingRegistration.cs | 25 ++- src/Artemis.Core/Models/Profile/Layer.cs | 20 ++- .../Profile/LayerProperties/ILayerProperty.cs | 11 ++ .../Profile/LayerProperties/LayerProperty.cs | 32 +--- .../Models/Profile/LayerPropertyGroup.cs | 5 + .../Internal/PropertiesLayerBrush.cs | 1 + .../LayerBrushes/LayerBrushDescriptor.cs | 8 +- .../LayerBrushes/LayerBrushProvider.cs | 5 +- .../LayerEffects/LayerEffectDescriptor.cs | 10 +- .../LayerEffects/LayerEffectProvider.cs | 5 +- src/Artemis.Core/Services/CoreService.cs | 9 +- .../Registration/LayerBrushService.cs | 10 +- .../Registration/LayerEffectService.cs | 9 -- .../Interfaces/IProfileEditorService.cs | 10 ++ .../Services/ProfileEditorService.cs | 33 +++- .../Ninject/Factories/IVMFactory.cs | 8 +- .../LayerPropertyViewModelInstanceProvider.cs | 32 ++++ src/Artemis.UI/Ninject/UiModule.cs | 4 + .../DataBindings/DataBindingsViewModel.cs | 39 +++-- .../LayerPropertiesViewModel.cs | 147 +++++------------- .../LayerProperties/LayerPropertyViewModel.cs | 16 +- .../Timeline/TimelineGroupView.xaml | 10 +- .../Timeline/TimelinePropertyView.xaml | 1 - .../Timeline/TimelinePropertyViewModel.cs | 1 + .../Timeline/TimelineSegmentViewModel.cs | 20 ++- .../Timeline/TimelineView.xaml | 2 +- .../Timeline/TimelineViewModel.cs | 35 +++-- .../Tree/TreeGroupViewModel.cs | 2 + .../LayerProperties/Tree/TreeView.xaml | 2 +- .../ProfileEditor/ProfileEditorViewModel.cs | 23 +-- .../Debug/Tabs/DataModelDebugViewModel.cs | 2 +- .../Services/RegistrationService.cs | 1 + 38 files changed, 309 insertions(+), 352 deletions(-) delete mode 100644 pointers.json create mode 100644 src/Artemis.UI/Ninject/InstanceProviders/LayerPropertyViewModelInstanceProvider.cs diff --git a/pointers.json b/pointers.json deleted file mode 100644 index 5d2c2f369..000000000 --- a/pointers.json +++ /dev/null @@ -1,75 +0,0 @@ -// Pointers used to auto-update. -// NOTE: If you're going to copy paste these for your own application, include a link to https://github.com/SpoinkyNL/Artemis -[ - { - "Game":"RocketLeague", - "GameVersion":"1.30", - "GameAddresses":[ - { - "Description":"Boost", - "BasePointer":{ - "value":23986356 - }, - "Offsets":[ - 196, - 24, - 904, - 1852, - 548 - ] - } - ] - }, - { - "Game":"WorldOfWarcraft", - "GameVersion":"7.0.3.22810", - "GameAddresses":[ - { - "Description":"ObjectManager", - "BasePointer":{ - "value":22511728 - }, - "Offsets":null - }, - { - "Description":"LocalPlayer", - "BasePointer":{ - "value":23715600 - }, - "Offsets":null - }, - { - "Description":"NameCache", - "BasePointer":{ - "value":22142184 - }, - "Offsets":null - }, - { - "Description":"TargetGuid", - "BasePointer":{ - "value":24758592 - }, - "Offsets":null - } - ] - }, - { - "Game":"Terraria", - "GameVersion":"1.3.4.4", - "GameAddresses":[ - { - "Description":"PlayerBase", - "BasePointer":{ - "value":3784824 - }, - "Offsets":[ - 640, - 1728, - 1652, - 60 - ] - } - ] - } -] diff --git a/src/Artemis.Core/Artemis.Core.csproj b/src/Artemis.Core/Artemis.Core.csproj index baa5c2106..99bdd68c0 100644 --- a/src/Artemis.Core/Artemis.Core.csproj +++ b/src/Artemis.Core/Artemis.Core.csproj @@ -37,6 +37,7 @@ + diff --git a/src/Artemis.Core/Models/Profile/DataBindings/Converters/FloatDataBindingConverter.cs b/src/Artemis.Core/Models/Profile/DataBindings/Converters/FloatDataBindingConverter.cs index e480ccc18..bd061eaa4 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/Converters/FloatDataBindingConverter.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/Converters/FloatDataBindingConverter.cs @@ -41,13 +41,13 @@ namespace Artemis.Core if (DataBinding.LayerProperty.PropertyDescription.MinInputValue is float min) value = Math.Max(value, min); - ValueSetter?.Invoke(value); + SetExpression?.Invoke(value); } /// public override float GetValue() { - return ValueGetter?.Invoke() ?? 0f; + return GetExpression(DataBinding.LayerProperty.CurrentValue); } } } \ 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 839d3a14b..5d5ec636c 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/Converters/GeneralDataBindingConverter.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/Converters/GeneralDataBindingConverter.cs @@ -26,13 +26,13 @@ namespace Artemis.Core /// public override void ApplyValue(object value) { - ValueSetter?.Invoke(value); + SetExpression?.Invoke(value); } /// public override object GetValue() { - return ValueGetter?.Invoke(); + return GetExpression(DataBinding.LayerProperty.CurrentValue); } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/Converters/IntDataBindingConverter.cs b/src/Artemis.Core/Models/Profile/DataBindings/Converters/IntDataBindingConverter.cs index 0c452e08d..2bd6bd483 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/Converters/IntDataBindingConverter.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/Converters/IntDataBindingConverter.cs @@ -3,12 +3,12 @@ namespace Artemis.Core { /// - public class IntDataBindingConverter : FloatDataBindingConverter + public class IntDataBindingConverter : IntDataBindingConverter { } /// - public class IntDataBindingConverter : DataBindingConverter where T : ILayerProperty + public class IntDataBindingConverter : DataBindingConverter { /// /// Creates a new instance of the class @@ -41,13 +41,13 @@ namespace Artemis.Core /// public override void ApplyValue(int value) { - ValueSetter?.Invoke(value); + SetExpression?.Invoke(value); } /// public override int GetValue() { - return ValueGetter?.Invoke() ?? 0; + return GetExpression(DataBinding.LayerProperty.CurrentValue); } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs index 4e5194a24..e5d3f3b13 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs @@ -13,12 +13,12 @@ namespace Artemis.Core /// /// A dynamically compiled getter pointing to the data bound property /// - public Func ValueGetter { get; private set; } + public Func GetExpression { get; private set; } /// /// A dynamically compiled setter pointing to the data bound property /// - public Action ValueSetter { get; private set; } + public Action SetExpression { get; private set; } /// /// Gets the data binding this converter is applied to @@ -75,32 +75,12 @@ namespace Artemis.Core internal void Initialize(DataBinding dataBinding) { DataBinding = dataBinding; - ValueGetter = CreateValueGetter(); - ValueSetter = CreateValueSetter(); + GetExpression = dataBinding.Registration.PropertyExpression.Compile(); + SetExpression = CreateValueSetter(); OnInitialized(); } - private Func CreateValueGetter() - { - if (DataBinding.TargetProperty?.DeclaringType == null) - return null; - - 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) - : (MemberExpression) DataBinding.Registration.Path.Split('.').Aggregate(constant, Expression.Property); - - var lambda = Expression.Lambda>(property); - - return lambda.Compile(); - } - private Action CreateValueSetter() { if (DataBinding.TargetProperty?.DeclaringType == null) @@ -110,10 +90,9 @@ namespace Artemis.Core if (setterMethod == null) return null; - var constant = Expression.Constant(DataBinding.LayerProperty); var propertyValue = Expression.Parameter(typeof(TProperty), "propertyValue"); - var body = Expression.Call(constant, setterMethod, propertyValue); + var body = DataBinding.Registration.PropertyExpression; var lambda = Expression.Lambda>(body, propertyValue); return lambda.Compile(); } diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs index 9944d49b8..dba41727f 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Linq.Expressions; using System.Reflection; namespace Artemis.Core @@ -7,16 +8,13 @@ namespace Artemis.Core /// public class DataBindingRegistration : IDataBindingRegistration { - internal DataBindingRegistration( - LayerProperty layerProperty, - DataBindingConverter converter, - PropertyInfo property, - string path) + internal DataBindingRegistration(LayerProperty layerProperty, + DataBindingConverter converter, + Expression> propertyExpression) { LayerProperty = layerProperty ?? throw new ArgumentNullException(nameof(layerProperty)); Converter = converter ?? throw new ArgumentNullException(nameof(converter)); - Property = property ?? throw new ArgumentNullException(nameof(property)); - Path = path ?? throw new ArgumentNullException(nameof(path)); + PropertyExpression = propertyExpression ?? throw new ArgumentNullException(nameof(propertyExpression)); } /// @@ -24,22 +22,21 @@ namespace Artemis.Core /// public LayerProperty LayerProperty { get; } - /// /// Gets the converter that's used by the data binding /// public DataBindingConverter Converter { get; } + /// + /// Gets the expression that that accesses the property + /// + public Expression> PropertyExpression { get; } + /// /// Gets the registered property /// public PropertyInfo Property { get; } - /// - /// Gets the path of the registered property on the layer property - /// - public string Path { get; } - /// /// Gets the data binding created using this registration /// @@ -51,7 +48,7 @@ namespace Artemis.Core if (DataBinding != null) return DataBinding; - var dataBinding = LayerProperty.Entity.DataBindingEntities.FirstOrDefault(e => e.TargetProperty == Path); + var dataBinding = LayerProperty.Entity.DataBindingEntities.FirstOrDefault(e => e.TargetProperty == PropertyExpression.ToString()); if (dataBinding == null) return null; diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index 1cd1e5445..ac2c141fc 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -64,8 +64,8 @@ namespace Artemis.Core _leds = new List(); _expandedPropertyGroups = new List(); - Initialize(); Load(); + Initialize(); } internal LayerEntity LayerEntity { get; set; } @@ -148,11 +148,22 @@ namespace Artemis.Core LayerBrushStore.LayerBrushRemoved += LayerBrushStoreOnLayerBrushRemoved; // Layers have two hardcoded property groups, instantiate them + var generalAttribute = Attribute.GetCustomAttribute( + GetType().GetProperty(nameof(General)), + typeof(PropertyGroupDescriptionAttribute) + ); + var transformAttribute = Attribute.GetCustomAttribute( + GetType().GetProperty(nameof(Transform)), + typeof(PropertyGroupDescriptionAttribute) + ); + General.GroupDescription = (PropertyGroupDescriptionAttribute) generalAttribute; General.Initialize(this, "General.", Constants.CorePluginInfo); + Transform.GroupDescription = (PropertyGroupDescriptionAttribute) transformAttribute; Transform.Initialize(this, "Transform.", Constants.CorePluginInfo); General.ShapeType.BaseValueChanged += ShapeTypeOnBaseValueChanged; ApplyShapeType(); + ActivateLayerBrush(); } #region Storage @@ -165,8 +176,6 @@ namespace Artemis.Core Order = LayerEntity.Order; _expandedPropertyGroups.AddRange(LayerEntity.ExpandedPropertyGroups); - ActivateLayerBrush(); - LoadRenderElement(); } @@ -710,6 +719,9 @@ namespace Artemis.Core internal void ActivateLayerBrush() { var current = General.BrushReference.CurrentValue; + if (current == null) + return; + var descriptor = LayerBrushStore.Get(current.BrushPluginGuid, current.BrushType)?.LayerBrushDescriptor; descriptor?.CreateInstance(this); @@ -729,7 +741,7 @@ namespace Artemis.Core } #endregion - + #region Event handlers private void LayerBrushStoreOnLayerBrushRemoved(object sender, LayerBrushStoreEvent e) diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs index 4a4c43130..9624bd576 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Artemis.Storage.Entities.Profile; namespace Artemis.Core @@ -20,5 +21,15 @@ namespace Artemis.Core /// /// void Initialize(RenderProfileElement profileElement, LayerPropertyGroup group, PropertyEntity entity, bool fromStorage, PropertyDescriptionAttribute description); + + /// + /// Returns a list off all data binding registrations + /// + List GetAllDataBindingRegistrations(); + + /// + /// Gets or sets whether the property is hidden in the UI + /// + bool IsHidden { get; set; } } } \ 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 339401608..2737daf91 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs @@ -61,9 +61,7 @@ namespace Artemis.Core private bool _isHidden; - /// - /// Gets or sets whether the property is hidden in the UI - /// + /// public bool IsHidden { get => _isHidden; @@ -335,36 +333,21 @@ namespace Artemis.Core return _dataBindingRegistrations; } - public void RegisterDataBindingProperty(Expression> propertyLambda, DataBindingConverter converter) + public void RegisterDataBindingProperty(Expression> propertyExpression, DataBindingConverter converter) { if (_disposed) throw new ObjectDisposedException("LayerProperty"); - // 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); - // Deconstruct the lambda - var current = (MemberExpression) propertyLambda.Body; - path = current.Member.Name; - while (current.Expression is MemberExpression memberExpression) - { - path = current.Member.Name + "." + path; - current = memberExpression; - } - } + 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 != propertyInfo.PropertyType) + if (converter.SupportedType != propertyExpression.ReturnType) { - throw new ArtemisCoreException($"Cannot register data binding property for property {propertyInfo.Name} " + + 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, propertyInfo, path)); + _dataBindingRegistrations.Add(new DataBindingRegistration(this, converter, propertyExpression)); } /// @@ -432,7 +415,6 @@ namespace Artemis.Core Entity = entity ?? throw new ArgumentNullException(nameof(entity)); PropertyDescription = description ?? throw new ArgumentNullException(nameof(description)); IsLoadedFromStorage = fromStorage; - LayerPropertyGroup.PropertyGroupUpdating += (sender, args) => Update(args.DeltaTime); } diff --git a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs index bf423342c..1b4951458 100644 --- a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs +++ b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs @@ -7,6 +7,7 @@ using Artemis.Core.LayerBrushes; using Artemis.Core.LayerEffects; using Artemis.Core.Properties; using Artemis.Storage.Entities.Profile; +using Humanizer; namespace Artemis.Core { @@ -215,6 +216,10 @@ namespace Artemis.Core if (instance == null) throw new ArtemisPluginException($"Failed to create instance of layer property at {path + propertyInfo.Name}"); + // Ensure the description has a name, if not this is a good point to set it based on the property info + if (string.IsNullOrWhiteSpace(propertyDescription.Name)) + propertyDescription.Name = propertyInfo.Name.Humanize(); + var entity = GetPropertyEntity(ProfileElement, path + propertyInfo.Name, out var fromStorage); instance.Initialize(ProfileElement, this, entity, fromStorage, propertyDescription); propertyInfo.SetValue(this, instance); diff --git a/src/Artemis.Core/Plugins/LayerBrushes/Internal/PropertiesLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/Internal/PropertiesLayerBrush.cs index 900f8bd9c..88c2d3421 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/Internal/PropertiesLayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrushes/Internal/PropertiesLayerBrush.cs @@ -35,6 +35,7 @@ namespace Artemis.Core.LayerBrushes internal void InitializeProperties() { Properties = Activator.CreateInstance(); + Properties.GroupDescription ??= new PropertyGroupDescriptionAttribute {Name = Descriptor.DisplayName, Description = Descriptor.Description}; Properties.LayerBrush = this; Properties.Initialize(Layer, "LayerBrush.", PluginInfo); PropertiesInitialized = true; diff --git a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushDescriptor.cs b/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushDescriptor.cs index a4173e75c..f0fd29539 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushDescriptor.cs +++ b/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushDescriptor.cs @@ -1,4 +1,5 @@ using System; +using Artemis.Core.Services; using Ninject; namespace Artemis.Core.LayerBrushes @@ -43,11 +44,6 @@ namespace Artemis.Core.LayerBrushes /// public LayerBrushProvider LayerBrushProvider { get; } - /// - /// Gets or sets the kernel used to instantiate the described layer brush - /// - internal IKernel Kernel { get; set; } - /// /// Creates an instance of the described brush and applies it to the layer /// @@ -56,7 +52,7 @@ namespace Artemis.Core.LayerBrushes if (layer.LayerBrush != null) throw new ArtemisCoreException("Layer already has an instantiated layer brush"); - var brush = (BaseLayerBrush) Kernel.Get(LayerBrushType); + var brush = (BaseLayerBrush) CoreService.Kernel.Get(LayerBrushType); brush.Layer = layer; brush.Descriptor = this; brush.Initialize(); diff --git a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushProvider.cs b/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushProvider.cs index 8470cc57e..3766edb13 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushProvider.cs +++ b/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushProvider.cs @@ -41,11 +41,14 @@ namespace Artemis.Core.LayerBrushes if (!Enabled) throw new ArtemisPluginException(PluginInfo, "Can only add a layer brush descriptor when the plugin is enabled"); - _layerBrushDescriptors.Add(new LayerBrushDescriptor(displayName, description, icon, typeof(T), this)); + var descriptor = new LayerBrushDescriptor(displayName, description, icon, typeof(T), this); + _layerBrushDescriptors.Add(descriptor); + LayerBrushStore.Add(descriptor); } private void OnPluginDisabled(object sender, EventArgs e) { + // The store will clean up the registrations by itself, the plugin just needs to clear its own list _layerBrushDescriptors.Clear(); } } diff --git a/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs b/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs index 808869b6f..21cb29d7d 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs +++ b/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using Artemis.Core.Services; using Artemis.Storage.Entities.Profile; using Ninject; @@ -44,12 +45,7 @@ namespace Artemis.Core.LayerEffects /// The plugin that provided this /// public LayerEffectProvider LayerEffectProvider { get; } - - /// - /// Gets or sets the kernel used to instantiate the described layer effect - /// - internal IKernel Kernel { get; set; } - + /// /// Gets a boolean indicating if this descriptor is a placeholder for a missing plugin /// @@ -70,7 +66,7 @@ namespace Artemis.Core.LayerEffects return; } - var effect = (BaseLayerEffect) Kernel.Get(LayerEffectType); + var effect = (BaseLayerEffect)CoreService.Kernel.Get(LayerEffectType); effect.ProfileElement = renderElement; effect.EntityId = entity.Id; effect.Order = entity.Order; diff --git a/src/Artemis.Core/Plugins/LayerEffects/LayerEffectProvider.cs b/src/Artemis.Core/Plugins/LayerEffects/LayerEffectProvider.cs index e28716ce9..91f86414c 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/LayerEffectProvider.cs +++ b/src/Artemis.Core/Plugins/LayerEffects/LayerEffectProvider.cs @@ -41,11 +41,14 @@ namespace Artemis.Core.LayerEffects if (!Enabled) throw new ArtemisPluginException(PluginInfo, "Can only add a layer effect descriptor when the plugin is enabled"); - _layerEffectDescriptors.Add(new LayerEffectDescriptor(displayName, description, icon, typeof(T), this)); + var descriptor = new LayerEffectDescriptor(displayName, description, icon, typeof(T), this); + _layerEffectDescriptors.Add(descriptor); + LayerEffectStore.Add(descriptor); } private void OnPluginDisabled(object sender, EventArgs e) { + // The store will clean up the registrations by itself, the plugin just needs to clear its own list _layerEffectDescriptors.Clear(); } } diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index eaf195c28..e981a567e 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -23,8 +23,9 @@ namespace Artemis.Core.Services /// internal class CoreService : ICoreService { + internal static IKernel Kernel; + private readonly Stopwatch _frameStopWatch; - private readonly IKernel _kernel; private readonly ILogger _logger; private readonly PluginSetting _loggingLevel; private readonly IPluginService _pluginService; @@ -37,9 +38,9 @@ namespace Artemis.Core.Services // ReSharper disable once UnusedParameter.Local - Storage migration service is injected early to ensure it runs before anything else public CoreService(IKernel kernel, ILogger logger, StorageMigrationService _, ISettingsService settingsService, IPluginService pluginService, - IRgbService rgbService, ISurfaceService surfaceService, IProfileService profileService) + IRgbService rgbService, ISurfaceService surfaceService, IProfileService profileService, IModuleService moduleService) { - _kernel = kernel; + Kernel = kernel; _logger = logger; _pluginService = pluginService; _rgbService = rgbService; @@ -80,7 +81,7 @@ namespace Artemis.Core.Services _logger.Information("Initializing Artemis Core version {version}", versionAttribute?.InformationalVersion); ApplyLoggingLevel(); - DeserializationLogger.Initialize(_kernel); + DeserializationLogger.Initialize(Kernel); // Initialize the services _pluginService.CopyBuiltInPlugins(); diff --git a/src/Artemis.Core/Services/Registration/LayerBrushService.cs b/src/Artemis.Core/Services/Registration/LayerBrushService.cs index 582a988ff..b46d24143 100644 --- a/src/Artemis.Core/Services/Registration/LayerBrushService.cs +++ b/src/Artemis.Core/Services/Registration/LayerBrushService.cs @@ -2,24 +2,16 @@ using System.Collections.Generic; using System.Linq; using Artemis.Core.LayerBrushes; -using Ninject; namespace Artemis.Core.Services { internal class LayerBrushService : ILayerBrushService { - private readonly IKernel _kernel; - - public LayerBrushService(IKernel kernel) - { - _kernel = kernel; - } public LayerBrushRegistration RegisterLayerBrush(LayerBrushDescriptor descriptor) { if (descriptor == null) throw new ArgumentNullException(nameof(descriptor)); - descriptor.Kernel = _kernel; return LayerBrushStore.Add(descriptor); } @@ -35,4 +27,4 @@ namespace Artemis.Core.Services return LayerBrushStore.GetAll().Select(r => r.LayerBrushDescriptor).ToList(); } } -} +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/Registration/LayerEffectService.cs b/src/Artemis.Core/Services/Registration/LayerEffectService.cs index 160c712df..a845776b0 100644 --- a/src/Artemis.Core/Services/Registration/LayerEffectService.cs +++ b/src/Artemis.Core/Services/Registration/LayerEffectService.cs @@ -2,25 +2,16 @@ using System.Collections.Generic; using System.Linq; using Artemis.Core.LayerEffects; -using Ninject; namespace Artemis.Core.Services { internal class LayerEffectService : ILayerEffectService { - private readonly IKernel _kernel; - - public LayerEffectService(IKernel kernel) - { - _kernel = kernel; - } - public LayerEffectRegistration RegisterLayerEffect(LayerEffectDescriptor descriptor) { if (descriptor == null) throw new ArgumentNullException(nameof(descriptor)); - descriptor.Kernel = _kernel; return LayerEffectStore.Add(descriptor); } diff --git a/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs b/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs index ce1f49197..85cf152e0 100644 --- a/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs @@ -74,6 +74,16 @@ namespace Artemis.UI.Shared.Services /// PropertyInputRegistration RegisterPropertyInput(PluginInfo pluginInfo) where T : PropertyInputViewModel; + /// + /// Registers a new property input view model used in the profile editor for the generic type defined in + /// + /// Note: Registration will remove itself on plugin disable so you don't have to + /// + /// + /// + /// + PropertyInputRegistration RegisterPropertyInput(Type viewModelType, PluginInfo pluginInfo); + void RemovePropertyInput(PropertyInputRegistration registration); /// diff --git a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs index 23004586a..5eafbff57 100644 --- a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs @@ -196,10 +196,25 @@ namespace Artemis.UI.Shared.Services public PropertyInputRegistration RegisterPropertyInput(PluginInfo pluginInfo) where T : PropertyInputViewModel { - var viewModelType = typeof(T); + return RegisterPropertyInput(typeof(T), pluginInfo); + } + + public PropertyInputRegistration RegisterPropertyInput(Type viewModelType, PluginInfo pluginInfo) + { + if (!typeof(PropertyInputViewModel).IsAssignableFrom(viewModelType)) + throw new ArtemisSharedUIException($"Property input VM type must implement {nameof(PropertyInputViewModel)}"); + lock (_registeredPropertyEditors) { var supportedType = viewModelType.BaseType.GetGenericArguments()[0]; + // If the supported type is a generic, assume there is a base type + if (supportedType.IsGenericParameter) + { + if (supportedType.BaseType == null) + throw new ArtemisSharedUIException($"Generic property input VM type must have a type constraint"); + supportedType = supportedType.BaseType; + } + var existing = _registeredPropertyEditors.FirstOrDefault(r => r.SupportedType == supportedType); if (existing != null) { @@ -267,12 +282,24 @@ namespace Artemis.UI.Shared.Services public PropertyInputViewModel CreatePropertyInputViewModel(LayerProperty layerProperty) { + Type viewModelType = null; var registration = RegisteredPropertyEditors.FirstOrDefault(r => r.SupportedType == typeof(T)); - if (registration == null) + + // Check for enums if no supported type was found + if (registration == null && typeof(T).IsEnum) + { + // The enum VM will likely be a generic, that requires creating a generic type matching the layer property + registration = RegisteredPropertyEditors.FirstOrDefault(r => r.SupportedType == typeof(Enum)); + if (registration != null && registration.ViewModelType.IsGenericType) + viewModelType = registration.ViewModelType.MakeGenericType(layerProperty.GetType().GenericTypeArguments); + } + else if (registration != null) + viewModelType = registration.ViewModelType; + else return null; var parameter = new ConstructorArgument("layerProperty", layerProperty); - return (PropertyInputViewModel) Kernel.Get(registration.ViewModelType, parameter); + return (PropertyInputViewModel) Kernel.Get(viewModelType, parameter); } public ProfileModule GetCurrentModule() diff --git a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs index ef45ad046..7fbb05c43 100644 --- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -82,10 +82,16 @@ namespace Artemis.UI.Ninject.Factories LayerPropertyGroupViewModel LayerPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup); TreeGroupViewModel TreeGroupViewModel(LayerPropertyGroupViewModel layerPropertyGroupViewModel); TimelineGroupViewModel TimelineGroupViewModel(LayerPropertyGroupViewModel layerPropertyGroupViewModel); - + TreeViewModel TreeViewModel(LayerPropertiesViewModel layerPropertiesViewModel, BindableCollection layerPropertyGroups); EffectsViewModel EffectsViewModel(LayerPropertiesViewModel layerPropertiesViewModel); TimelineViewModel TimelineViewModel(LayerPropertiesViewModel layerPropertiesViewModel, BindableCollection layerPropertyGroups); TimelineSegmentViewModel TimelineSegmentViewModel(SegmentViewModelType segment, BindableCollection layerPropertyGroups); } + + public interface IPropertyVmFactory + { + ITreePropertyViewModel TreePropertyViewModel(ILayerProperty layerProperty, LayerPropertyViewModel layerPropertyViewModel); + ITimelinePropertyViewModel TimelinePropertyViewModel(ILayerProperty layerProperty, LayerPropertyViewModel layerPropertyViewModel); + } } \ No newline at end of file diff --git a/src/Artemis.UI/Ninject/InstanceProviders/LayerPropertyViewModelInstanceProvider.cs b/src/Artemis.UI/Ninject/InstanceProviders/LayerPropertyViewModelInstanceProvider.cs new file mode 100644 index 000000000..34ed14551 --- /dev/null +++ b/src/Artemis.UI/Ninject/InstanceProviders/LayerPropertyViewModelInstanceProvider.cs @@ -0,0 +1,32 @@ +using System; +using System.Reflection; +using Artemis.Core; +using Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline; +using Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree; +using Ninject.Extensions.Factory; + +namespace Artemis.UI.Ninject.InstanceProviders +{ + public class LayerPropertyViewModelInstanceProvider : StandardInstanceProvider + { + protected override Type GetType(MethodInfo methodInfo, object[] arguments) + { + if (methodInfo.ReturnType != typeof(ITreePropertyViewModel) && methodInfo.ReturnType != typeof(ITimelinePropertyViewModel)) + return base.GetType(methodInfo, arguments); + + // Find LayerProperty type + var layerPropertyType = arguments[0].GetType(); + while (layerPropertyType != null && (!layerPropertyType.IsGenericType || layerPropertyType.GetGenericTypeDefinition() != typeof(LayerProperty<>))) + layerPropertyType = layerPropertyType.BaseType; + if (layerPropertyType == null) + return base.GetType(methodInfo, arguments); + + if (methodInfo.ReturnType == typeof(ITreePropertyViewModel)) + return typeof(TreePropertyViewModel<>).MakeGenericType(layerPropertyType.GetGenericArguments()); + if (methodInfo.ReturnType == typeof(ITimelinePropertyViewModel)) + return typeof(TimelinePropertyViewModel<>).MakeGenericType(layerPropertyType.GetGenericArguments()); + + return base.GetType(methodInfo, arguments); + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Ninject/UiModule.cs b/src/Artemis.UI/Ninject/UiModule.cs index 3ae26f21d..1da66b61c 100644 --- a/src/Artemis.UI/Ninject/UiModule.cs +++ b/src/Artemis.UI/Ninject/UiModule.cs @@ -1,5 +1,6 @@ using System; using Artemis.UI.Ninject.Factories; +using Artemis.UI.Ninject.InstanceProviders; using Artemis.UI.Screens; using Artemis.UI.Screens.ProfileEditor; using Artemis.UI.Services.Interfaces; @@ -7,6 +8,7 @@ using Artemis.UI.Shared.Services; using Artemis.UI.Stylet; using FluentValidation; using Ninject.Extensions.Conventions; +using Ninject.Extensions.Factory; using Ninject.Modules; using Stylet; @@ -48,6 +50,8 @@ namespace Artemis.UI.Ninject .BindToFactory(); }); + Kernel.Bind().ToFactory(() => new LayerPropertyViewModelInstanceProvider()); + // Bind profile editor VMs Kernel.Bind(x => { diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs index bebf2d871..badbda92c 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs @@ -1,32 +1,49 @@ using System; -using System.Linq; -using Artemis.Core; using Artemis.UI.Ninject.Factories; +using Artemis.UI.Shared.Services; using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings { - public class DataBindingsViewModel : Conductor.Collection.AllActive + public class DataBindingsViewModel : Conductor.Collection.AllActive { private readonly IDataBindingsVmFactory _dataBindingsVmFactory; - - public DataBindingsViewModel(LayerProperty layerProperty, IDataBindingsVmFactory dataBindingsVmFactory) + private readonly IProfileEditorService _profileEditorService; + + public DataBindingsViewModel(IProfileEditorService profileEditorService, IDataBindingsVmFactory dataBindingsVmFactory) { + _profileEditorService = profileEditorService; _dataBindingsVmFactory = dataBindingsVmFactory; - LayerProperty = layerProperty; - Initialise(); + + _profileEditorService.SelectedDataBindingChanged += ProfileEditorServiceOnSelectedDataBindingChanged; + CreateDataBindingViewModels(); } - public LayerProperty LayerProperty { get; } - - private void Initialise() + private void CreateDataBindingViewModels() { - var registrations = LayerProperty.GetAllDataBindingRegistrations(); + Items.Clear(); + + var layerProperty = _profileEditorService.SelectedDataBinding; + if (layerProperty == null) + return; + + var registrations = layerProperty.GetAllDataBindingRegistrations(); // Create a data binding VM for each data bindable property. These VMs will be responsible for retrieving // and creating the actual data bindings foreach (var registration in registrations) ActivateItem(_dataBindingsVmFactory.DataBindingViewModel(registration)); } + + protected override void OnClose() + { + _profileEditorService.SelectedDataBindingChanged -= ProfileEditorServiceOnSelectedDataBindingChanged; + base.OnClose(); + } + + private void ProfileEditorServiceOnSelectedDataBindingChanged(object? sender, EventArgs e) + { + CreateDataBindingViewModels(); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs index 0e5311a24..9e9094169 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs @@ -23,42 +23,62 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties public class LayerPropertiesViewModel : Conductor.Collection.AllActive, IProfileEditorPanelViewModel, IDropTarget { private readonly ILayerPropertyVmFactory _layerPropertyVmFactory; - private readonly IDataBindingsVmFactory _dataBindingsVmFactory; private LayerPropertyGroupViewModel _brushPropertyGroup; - private DataBindingsViewModel _dataBindingsViewModel; - private EffectsViewModel _effectsViewModel; - private TimelineSegmentViewModel _endTimelineSegmentViewModel; - private BindableCollection _layerPropertyGroups; - private TimelineSegmentViewModel _mainTimelineSegmentViewModel; private bool _playing; private int _propertyTreeIndex; private bool _repeatAfterLastKeyframe; private int _rightSideIndex; private RenderProfileElement _selectedProfileElement; - private TimelineSegmentViewModel _startTimelineSegmentViewModel; - private TimelineViewModel _timelineViewModel; - private TreeViewModel _treeViewModel; public LayerPropertiesViewModel(IProfileEditorService profileEditorService, ICoreService coreService, ISettingsService settingsService, ILayerPropertyVmFactory layerPropertyVmFactory, - IDataBindingsVmFactory dataBindingsVmFactory) + DataBindingsViewModel dataBindingsViewModel) { _layerPropertyVmFactory = layerPropertyVmFactory; - _dataBindingsVmFactory = dataBindingsVmFactory; ProfileEditorService = profileEditorService; CoreService = coreService; SettingsService = settingsService; - EffectsViewModel = _layerPropertyVmFactory.EffectsViewModel(this); - Items.Add(EffectsViewModel); - LayerPropertyGroups = new BindableCollection(); PropertyChanged += HandlePropertyTreeIndexChanged; + + // Left side + TreeViewModel = _layerPropertyVmFactory.TreeViewModel(this, LayerPropertyGroups); + EffectsViewModel = _layerPropertyVmFactory.EffectsViewModel(this); + Items.Add(TreeViewModel); + Items.Add(EffectsViewModel); + + // Right side + StartTimelineSegmentViewModel = _layerPropertyVmFactory.TimelineSegmentViewModel(SegmentViewModelType.Start, LayerPropertyGroups); + MainTimelineSegmentViewModel = _layerPropertyVmFactory.TimelineSegmentViewModel(SegmentViewModelType.Main, LayerPropertyGroups); + EndTimelineSegmentViewModel = _layerPropertyVmFactory.TimelineSegmentViewModel(SegmentViewModelType.End, LayerPropertyGroups); + TimelineViewModel = _layerPropertyVmFactory.TimelineViewModel(this, LayerPropertyGroups); + DataBindingsViewModel = dataBindingsViewModel; + Items.Add(StartTimelineSegmentViewModel); + Items.Add(MainTimelineSegmentViewModel); + Items.Add(EndTimelineSegmentViewModel); + Items.Add(TimelineViewModel); + Items.Add(DataBindingsViewModel); } + public BindableCollection LayerPropertyGroups { get; } + + + #region Child VMs + + public TreeViewModel TreeViewModel { get; } + public EffectsViewModel EffectsViewModel { get; } + public TimelineSegmentViewModel StartTimelineSegmentViewModel { get; } + public TimelineSegmentViewModel MainTimelineSegmentViewModel { get; } + public TimelineSegmentViewModel EndTimelineSegmentViewModel { get; } + public TimelineViewModel TimelineViewModel { get; } + public DataBindingsViewModel DataBindingsViewModel { get; } + + #endregion + public IProfileEditorService ProfileEditorService { get; } public ICoreService CoreService { get; } public ISettingsService SettingsService { get; } @@ -115,53 +135,6 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties public Layer SelectedLayer => SelectedProfileElement as Layer; public Folder SelectedFolder => SelectedProfileElement as Folder; - public BindableCollection LayerPropertyGroups - { - get => _layerPropertyGroups; - set => SetAndNotify(ref _layerPropertyGroups, value); - } - - public TreeViewModel TreeViewModel - { - get => _treeViewModel; - set => SetAndNotify(ref _treeViewModel, value); - } - - public EffectsViewModel EffectsViewModel - { - get => _effectsViewModel; - set => SetAndNotify(ref _effectsViewModel, value); - } - - public DataBindingsViewModel DataBindingsViewModel - { - get => _dataBindingsViewModel; - set => SetAndNotify(ref _dataBindingsViewModel, value); - } - - public TimelineViewModel TimelineViewModel - { - get => _timelineViewModel; - set => SetAndNotify(ref _timelineViewModel, value); - } - - public TimelineSegmentViewModel StartTimelineSegmentViewModel - { - get => _startTimelineSegmentViewModel; - set => SetAndNotify(ref _startTimelineSegmentViewModel, value); - } - - public TimelineSegmentViewModel MainTimelineSegmentViewModel - { - get => _mainTimelineSegmentViewModel; - set => SetAndNotify(ref _mainTimelineSegmentViewModel, value); - } - - public TimelineSegmentViewModel EndTimelineSegmentViewModel - { - get => _endTimelineSegmentViewModel; - set => SetAndNotify(ref _endTimelineSegmentViewModel, value); - } #region Segments @@ -197,18 +170,6 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties ProfileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged; PopulateProperties(null); - - TimelineViewModel?.Dispose(); - TimelineViewModel = null; - StartTimelineSegmentViewModel?.Dispose(); - StartTimelineSegmentViewModel = null; - MainTimelineSegmentViewModel?.Dispose(); - MainTimelineSegmentViewModel = null; - EndTimelineSegmentViewModel?.Dispose(); - EndTimelineSegmentViewModel = null; - DataBindingsViewModel?.Dispose(); - DataBindingsViewModel = null; - base.OnClose(); } @@ -242,15 +203,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties private void ProfileEditorServiceOnSelectedDataBindingChanged(object? sender, EventArgs e) { - if (ProfileEditorService.SelectedDataBinding != null) - { - RightSideIndex = 1; - DataBindingsViewModel?.Dispose(); - // TODO - // DataBindingsViewModel = _dataBindingsVmFactory.DataBindingsViewModel(ProfileEditorService.SelectedDataBinding); - } - else - RightSideIndex = 0; + RightSideIndex = ProfileEditorService.SelectedDataBinding != null ? 1 : 0; } #region View model managament @@ -293,23 +246,6 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties LayerPropertyGroups.Add(_layerPropertyVmFactory.LayerPropertyGroupViewModel(SelectedLayer.Transform)); } - TreeViewModel = _layerPropertyVmFactory.TreeViewModel(this, LayerPropertyGroups); - - DeactivateItem(TimelineViewModel); - DeactivateItem(StartTimelineSegmentViewModel); - DeactivateItem(MainTimelineSegmentViewModel); - DeactivateItem(EndTimelineSegmentViewModel); - - TimelineViewModel = _layerPropertyVmFactory.TimelineViewModel(this, LayerPropertyGroups); - ActivateItem(TimelineViewModel); - - StartTimelineSegmentViewModel?.Dispose(); - StartTimelineSegmentViewModel = _layerPropertyVmFactory.TimelineSegmentViewModel(SegmentViewModelType.Start, LayerPropertyGroups); - MainTimelineSegmentViewModel?.Dispose(); - MainTimelineSegmentViewModel = _layerPropertyVmFactory.TimelineSegmentViewModel(SegmentViewModelType.Main, LayerPropertyGroups); - EndTimelineSegmentViewModel?.Dispose(); - EndTimelineSegmentViewModel = _layerPropertyVmFactory.TimelineSegmentViewModel(SegmentViewModelType.End, LayerPropertyGroups); - ApplyLayerBrush(); ApplyEffects(); } @@ -361,21 +297,18 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties private void ApplyEffects() { - RenderProfileElement renderElement; - if (SelectedLayer != null) - renderElement = SelectedLayer; - else if (SelectedFolder != null) - renderElement = SelectedFolder; - else + if (SelectedProfileElement == null) return; // Remove VMs of effects no longer applied on the layer - var toRemove = LayerPropertyGroups.Where(l => l.LayerPropertyGroup.LayerEffect != null && !renderElement.LayerEffects.Contains(l.LayerPropertyGroup.LayerEffect)).ToList(); + var toRemove = LayerPropertyGroups + .Where(l => l.LayerPropertyGroup.LayerEffect != null && !SelectedProfileElement.LayerEffects.Contains(l.LayerPropertyGroup.LayerEffect)) + .ToList(); LayerPropertyGroups.RemoveRange(toRemove); foreach (var layerPropertyGroupViewModel in toRemove) layerPropertyGroupViewModel.Dispose(); - foreach (var layerEffect in renderElement.LayerEffects) + foreach (var layerEffect in SelectedProfileElement.LayerEffects) { if (LayerPropertyGroups.Any(l => l.LayerPropertyGroup.LayerEffect == layerEffect)) continue; diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs index 50eb8cdcd..f4da97c08 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs @@ -1,5 +1,6 @@ using System; using Artemis.Core; +using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline; using Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree; using Ninject; @@ -10,25 +11,20 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties { public class LayerPropertyViewModel : PropertyChangedBase, IDisposable { - public LayerPropertyViewModel(ILayerProperty layerProperty, IKernel kernel) + public LayerPropertyViewModel(ILayerProperty layerProperty, IPropertyVmFactory propertyVmFactory) { LayerProperty = layerProperty; - var parameter = new ConstructorArgument("layerProperty", LayerProperty); - var treeViewModelType = typeof(TreePropertyViewModel<>).MakeGenericType(layerProperty.GetType().GetGenericArguments()); - var timelineViewModelType = typeof(TimelinePropertyViewModel<>).MakeGenericType(layerProperty.GetType().GetGenericArguments()); - - TreePropertyViewModel = (ITreePropertyViewModel) kernel.Get(treeViewModelType, parameter); - TimelinePropertyViewModel = (ITimelinePropertyViewModel) kernel.Get(timelineViewModelType, parameter); + TreePropertyViewModel = propertyVmFactory.TreePropertyViewModel(layerProperty, this); + TimelinePropertyViewModel = propertyVmFactory.TimelinePropertyViewModel(layerProperty, this); } public ILayerProperty LayerProperty { get; } public ITreePropertyViewModel TreePropertyViewModel { get; } public ITimelinePropertyViewModel TimelinePropertyViewModel { get; } - public bool IsVisible { get; set; } - public bool IsExpanded { get; set; } - + public bool IsVisible => !LayerProperty.IsHidden; + public void Dispose() { TreePropertyViewModel?.Dispose(); diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineGroupView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineGroupView.xaml index 0715f4cb5..3de69b24d 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineGroupView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineGroupView.xaml @@ -60,10 +60,16 @@ - + - + diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyView.xaml index 6438ce3e8..1e36855e5 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyView.xaml @@ -9,7 +9,6 @@ xmlns:timeline="clr-namespace:Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" - Visibility="{Binding LayerPropertyBaseViewModel.IsVisible, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" MinWidth="{Binding Width}" HorizontalAlignment="Stretch"> diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs index c4426894a..059d75240 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs @@ -19,6 +19,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline LayerProperty = layerProperty; LayerPropertyViewModel = layerPropertyViewModel; + UpdateKeyframes(); } public List GetAllKeyframeViewModels() diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs index 5404a6322..383577c45 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs @@ -11,7 +11,7 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline { - public class TimelineSegmentViewModel : PropertyChangedBase, IDisposable + public class TimelineSegmentViewModel : Screen, IDisposable { private bool _draggingSegment; private bool _showDisableButton; @@ -43,7 +43,9 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline UpdateDisplay(); ProfileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged; - SelectedProfileElement.PropertyChanged += SelectedProfileElementOnPropertyChanged; + ProfileEditorService.ProfileElementSelected += ProfileEditorServiceOnProfileElementSelected; + if (ProfileEditorService.SelectedProfileElement != null) + ProfileEditorService.SelectedProfileElement.PropertyChanged += SelectedProfileElementOnPropertyChanged; } public RenderProfileElement SelectedProfileElement { get; } @@ -97,6 +99,9 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline { get { + if (SelectedProfileElement == null) + return 0; + return Segment switch { SegmentViewModelType.Start => 0, @@ -128,7 +133,9 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline public void Dispose() { ProfileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged; - SelectedProfileElement.PropertyChanged -= SelectedProfileElementOnPropertyChanged; + ProfileEditorService.ProfileElementSelected -= ProfileEditorServiceOnProfileElementSelected; + if (SelectedProfileElement != null) + SelectedProfileElement.PropertyChanged -= SelectedProfileElementOnPropertyChanged; } public void DisableSegment() @@ -260,6 +267,13 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline UpdateDisplay(); } + private void ProfileEditorServiceOnProfileElementSelected(object? sender, RenderProfileElementEventArgs e) + { + if (e.PreviousRenderProfileElement != null) + e.PreviousRenderProfileElement.PropertyChanged -= SelectedProfileElementOnPropertyChanged; + e.RenderProfileElement.PropertyChanged += SelectedProfileElementOnPropertyChanged; + } + private void SelectedProfileElementOnPropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(RenderProfileElement.StartSegmentLength) || diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineView.xaml index 5935fa23a..c1cd92277 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineView.xaml @@ -37,7 +37,7 @@ HorizontalAlignment="Left"> - + diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs index 711949901..e2ae1ae95 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Windows; -using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Shapes; @@ -27,13 +26,14 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline LayerPropertyGroups = layerPropertyGroups; SelectionRectangle = new RectangleGeometry(); - SelectedProfileElement = layerPropertiesViewModel.SelectedProfileElement; - SelectedProfileElement.PropertyChanged += SelectedProfileElementOnPropertyChanged; _profileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged; + _profileEditorService.ProfileElementSelected += ProfileEditorServiceOnProfileElementSelected; + if (_profileEditorService.SelectedProfileElement != null) + _profileEditorService.SelectedProfileElement.PropertyChanged += SelectedProfileElementOnPropertyChanged; } - public RenderProfileElement SelectedProfileElement { get; set; } + public RenderProfileElement SelectedProfileElement => _profileEditorService.SelectedProfileElement; public BindableCollection LayerPropertyGroups { get; } @@ -43,23 +43,25 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline set => SetAndNotify(ref _selectionRectangle, value); } - public double StartSegmentWidth => _profileEditorService.PixelsPerSecond * SelectedProfileElement.StartSegmentLength.TotalSeconds; + public double StartSegmentWidth => _profileEditorService.PixelsPerSecond * (SelectedProfileElement?.StartSegmentLength.TotalSeconds ?? 0); public double StartSegmentEndPosition => StartSegmentWidth; - public double MainSegmentWidth => _profileEditorService.PixelsPerSecond * SelectedProfileElement.MainSegmentLength.TotalSeconds; + public double MainSegmentWidth => _profileEditorService.PixelsPerSecond * (SelectedProfileElement?.MainSegmentLength.TotalSeconds ?? 0); public double MainSegmentEndPosition => StartSegmentWidth + MainSegmentWidth; - public double EndSegmentWidth => _profileEditorService.PixelsPerSecond * SelectedProfileElement.EndSegmentLength.TotalSeconds; + public double EndSegmentWidth => _profileEditorService.PixelsPerSecond * (SelectedProfileElement?.EndSegmentLength.TotalSeconds ?? 0); public double EndSegmentEndPosition => StartSegmentWidth + MainSegmentWidth + EndSegmentWidth; - public double TotalTimelineWidth => _profileEditorService.PixelsPerSecond * SelectedProfileElement.TimelineLength.TotalSeconds; + public double TotalTimelineWidth => _profileEditorService.PixelsPerSecond * (SelectedProfileElement?.TimelineLength.TotalSeconds ?? 0); - public bool StartSegmentEnabled => SelectedProfileElement.StartSegmentLength != TimeSpan.Zero; - public bool EndSegmentEnabled => SelectedProfileElement.EndSegmentLength != TimeSpan.Zero; + public bool StartSegmentEnabled => SelectedProfileElement?.StartSegmentLength != TimeSpan.Zero; + public bool EndSegmentEnabled => SelectedProfileElement?.EndSegmentLength != TimeSpan.Zero; public void Dispose() { _profileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged; - SelectedProfileElement.PropertyChanged -= SelectedProfileElementOnPropertyChanged; + _profileEditorService.ProfileElementSelected -= ProfileEditorServiceOnProfileElementSelected; + if (_profileEditorService.SelectedProfileElement != null) + _profileEditorService.SelectedProfileElement.PropertyChanged -= SelectedProfileElementOnPropertyChanged; } - + private void SelectedProfileElementOnPropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(_profileEditorService.SelectedProfileElement.StartSegmentLength)) @@ -98,6 +100,13 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline NotifyOfPropertyChange(nameof(TotalTimelineWidth)); } + private void ProfileEditorServiceOnProfileElementSelected(object? sender, RenderProfileElementEventArgs e) + { + if (e.PreviousRenderProfileElement != null) + e.PreviousRenderProfileElement.PropertyChanged -= SelectedProfileElementOnPropertyChanged; + e.RenderProfileElement.PropertyChanged += SelectedProfileElementOnPropertyChanged; + } + #region Command handlers public void KeyframeMouseDown(object sender, MouseButtonEventArgs e) @@ -321,7 +330,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline var viewModels = new List(); foreach (var layerPropertyGroupViewModel in LayerPropertyGroups) viewModels.AddRange(layerPropertyGroupViewModel.GetAllKeyframeViewModels(false)); - + return viewModels; } diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeGroupViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeGroupViewModel.cs index 57f27bd6e..07eb31138 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeGroupViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeGroupViewModel.cs @@ -35,6 +35,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree LayerPropertyGroupViewModel = layerPropertyGroupViewModel; LayerPropertyGroup = LayerPropertyGroupViewModel.LayerPropertyGroup; + + DetermineGroupType(); } public LayerPropertyGroupViewModel LayerPropertyGroupViewModel { get; } diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeView.xaml index 66ec6e3ac..0cf321bbf 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeView.xaml @@ -98,7 +98,7 @@ - + diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs index 4e459d2b8..f76a54e97 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs @@ -36,7 +36,10 @@ namespace Artemis.UI.Screens.ProfileEditor private PluginSetting _sidePanelsWidth; public ProfileEditorViewModel(ProfileModule module, - ICollection viewModels, + ProfileViewModel profileViewModel, + ProfileTreeViewModel profileTreeViewModel, + DisplayConditionsViewModel displayConditionsViewModel, + LayerPropertiesViewModel layerPropertiesViewModel, IProfileEditorService profileEditorService, IProfileService profileService, IDialogService dialogService, @@ -55,15 +58,17 @@ namespace Artemis.UI.Screens.ProfileEditor DialogService = dialogService; Profiles = new BindableCollection(); - - // Run this first to let VMs activate without causing constant UI updates - Items.AddRange(viewModels); - + // Populate the panels - ProfileViewModel = (ProfileViewModel) viewModels.First(vm => vm is ProfileViewModel); - ProfileTreeViewModel = (ProfileTreeViewModel) viewModels.First(vm => vm is ProfileTreeViewModel); - DisplayConditionsViewModel = (DisplayConditionsViewModel) viewModels.First(vm => vm is DisplayConditionsViewModel); - LayerPropertiesViewModel = (LayerPropertiesViewModel) viewModels.First(vm => vm is LayerPropertiesViewModel); + ProfileViewModel = profileViewModel; + ProfileTreeViewModel = profileTreeViewModel; + DisplayConditionsViewModel = displayConditionsViewModel; + LayerPropertiesViewModel = layerPropertiesViewModel; + + Items.Add(ProfileViewModel); + Items.Add(ProfileTreeViewModel); + Items.Add(DisplayConditionsViewModel); + Items.Add(LayerPropertiesViewModel); } public ProfileModule Module { get; } diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs index a962339f5..4ec06306d 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs @@ -99,7 +99,7 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs private void GetDataModel() { MainDataModel = SelectedModule != null - ? _dataModelUIService.GetPluginDataModelVisualization(SelectedModule) + ? _dataModelUIService.GetPluginDataModelVisualization(SelectedModule, false) : _dataModelUIService.GetMainDataModelVisualization(); } diff --git a/src/Artemis.UI/Services/RegistrationService.cs b/src/Artemis.UI/Services/RegistrationService.cs index e4bf69965..1b036c4df 100644 --- a/src/Artemis.UI/Services/RegistrationService.cs +++ b/src/Artemis.UI/Services/RegistrationService.cs @@ -55,6 +55,7 @@ namespace Artemis.UI.Services _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo); _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo); _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo); + _profileEditorService.RegisterPropertyInput(typeof(EnumPropertyInputViewModel<>), Constants.CorePluginInfo); _registeredBuiltInPropertyEditors = true; } From fea454ad12758dd7abb3953abfa9e6d7a69e3b91 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Mon, 14 Sep 2020 01:24:07 +0200 Subject: [PATCH 11/12] Profile editor - Finished most of the refactor except databindings --- .../Artemis.Core.csproj.DotSettings | 1 + src/Artemis.Core/Constants.cs | 8 +- .../Converters/FloatDataBindingConverter.cs | 14 +- .../Converters/GeneralDataBindingConverter.cs | 2 +- .../Converters/IntDataBindingConverter.cs | 2 +- .../Profile/DataBindings/DataBinding.cs | 46 ++-- .../DataBindings/DataBindingConverter.cs | 30 ++- .../DataBindings/DataBindingModifier.cs | 6 +- .../DataBindings/DataBindingRegistration.cs | 18 +- src/Artemis.Core/Models/Profile/Folder.cs | 12 +- src/Artemis.Core/Models/Profile/Layer.cs | 24 +- .../Models/Profile/LayerBrushReference.cs | 10 + .../Profile/LayerProperties/ILayerProperty.cs | 5 - .../Profile/LayerProperties/LayerProperty.cs | 8 +- .../Models/Profile/LayerPropertyGroup.cs | 4 + .../Models/Profile/RenderProfileElement.cs | 68 ++++-- .../LayerBrushes/LayerBrushDescriptor.cs | 7 + .../LayerEffects/Internal/BaseLayerEffect.cs | 4 +- .../LayerEffects/LayerEffectDescriptor.cs | 10 +- .../Placeholder/PlaceholderLayerEffect.cs | 29 ++- .../PlaceholderLayerEffectDescriptor.cs | 12 +- src/Artemis.Core/Plugins/Plugin.cs | 2 +- .../Interfaces/ILayerBrushService.cs | 5 + .../Registration/LayerBrushService.cs | 18 ++ .../Services/Storage/ProfileService.cs | 15 +- src/Artemis.Core/Utilities/CorePlugin.cs | 40 ++++ .../Profile/DataBindings/DataBindingEntity.cs | 2 +- .../Ninject/Factories/IVMFactory.cs | 12 +- .../DataBindingsViewModelInstanceProvider.cs | 14 +- src/Artemis.UI/Ninject/UiModule.cs | 1 + .../PropertyInput/BrushPropertyInputView.xaml | 31 +-- .../BrushPropertyInputViewModel.cs | 15 +- .../LayerBrushDescriptors.xaml | 27 +++ .../DisplayConditionsViewModel.cs | 17 +- .../DataBindings/DataBindingView.xaml | 6 +- .../DataBindings/DataBindingViewModel.cs | 22 +- .../DataBindings/DataBindingsView.xaml | 2 +- .../DataBindings/DataBindingsViewModel.cs | 9 + .../LayerEffects/EffectsView.xaml | 2 +- .../LayerPropertiesViewModel.cs | 2 +- .../LayerPropertyGroupViewModel.cs | 153 +++++++------ .../LayerProperties/LayerPropertyViewModel.cs | 12 +- .../Timeline/TimelineGroupViewModel.cs | 39 +++- .../Timeline/TimelineKeyframeViewModel.cs | 38 ++-- .../Timeline/TimelinePropertyView.xaml | 2 +- .../Timeline/TimelinePropertyViewModel.cs | 28 ++- .../Timeline/TimelineSegmentViewModel.cs | 209 ++++++++++-------- .../Timeline/TimelineView.xaml | 4 +- .../Timeline/TimelineViewModel.cs | 142 +++++++----- .../Tree/TreePropertyViewModel.cs | 9 + .../ProfileTree/TreeItem/FolderViewModel.cs | 7 +- .../ProfileTree/TreeItem/LayerViewModel.cs | 7 +- .../ProfileTree/TreeItem/TreeItemViewModel.cs | 15 +- .../Tools/SelectionToolViewModel.cs | 31 ++- .../Tabs/General/GeneralSettingsTabView.xaml | 41 ++++ .../General/GeneralSettingsTabViewModel.cs | 26 ++- 56 files changed, 866 insertions(+), 459 deletions(-) create mode 100644 src/Artemis.Core/Utilities/CorePlugin.cs create mode 100644 src/Artemis.UI/ResourceDictionaries/LayerBrushDescriptors.xaml diff --git a/src/Artemis.Core/Artemis.Core.csproj.DotSettings b/src/Artemis.Core/Artemis.Core.csproj.DotSettings index 7f651acb6..abb086d59 100644 --- a/src/Artemis.Core/Artemis.Core.csproj.DotSettings +++ b/src/Artemis.Core/Artemis.Core.csproj.DotSettings @@ -23,6 +23,7 @@ True True True + True True True True diff --git a/src/Artemis.Core/Constants.cs b/src/Artemis.Core/Constants.cs index 0fa81409d..267ec0b76 100644 --- a/src/Artemis.Core/Constants.cs +++ b/src/Artemis.Core/Constants.cs @@ -32,7 +32,13 @@ namespace Artemis.Core /// /// The plugin info used by core components of Artemis /// - public static readonly PluginInfo CorePluginInfo = new PluginInfo {Guid = Guid.Parse("ffffffff-ffff-ffff-ffff-ffffffffffff"), Name = "Artemis Core"}; + public static readonly PluginInfo CorePluginInfo = new PluginInfo + { + Guid = Guid.Parse("ffffffff-ffff-ffff-ffff-ffffffffffff"), Name = "Artemis Core", Enabled = true + }; + + internal static readonly CorePlugin CorePlugin = new CorePlugin {PluginInfo = CorePluginInfo}; + internal static readonly EffectPlaceholderPlugin EffectPlaceholderPlugin = new EffectPlaceholderPlugin {PluginInfo = CorePluginInfo}; /// /// A read-only collection containing all primitive numeric types diff --git a/src/Artemis.Core/Models/Profile/DataBindings/Converters/FloatDataBindingConverter.cs b/src/Artemis.Core/Models/Profile/DataBindings/Converters/FloatDataBindingConverter.cs index bd061eaa4..7a0c3d0a8 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/Converters/FloatDataBindingConverter.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/Converters/FloatDataBindingConverter.cs @@ -1,4 +1,5 @@ using System; +using SkiaSharp; namespace Artemis.Core { @@ -36,12 +37,23 @@ namespace Artemis.Core /// public override void ApplyValue(float value) { + if (SetExpression == null) + return; + if (DataBinding.LayerProperty.PropertyDescription.MaxInputValue is float max) value = Math.Min(value, max); if (DataBinding.LayerProperty.PropertyDescription.MinInputValue is float min) value = Math.Max(value, min); - SetExpression?.Invoke(value); + var test = new SKSize(5,5); + test.Width = 10; + + SetExpression(DataBinding.LayerProperty.CurrentValue, value); + } + + private void Mehtest(ref SKSize test) + { + test.Width = 20; } /// diff --git a/src/Artemis.Core/Models/Profile/DataBindings/Converters/GeneralDataBindingConverter.cs b/src/Artemis.Core/Models/Profile/DataBindings/Converters/GeneralDataBindingConverter.cs index 5d5ec636c..bb94f06ac 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/Converters/GeneralDataBindingConverter.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/Converters/GeneralDataBindingConverter.cs @@ -26,7 +26,7 @@ namespace Artemis.Core /// public override void ApplyValue(object value) { - SetExpression?.Invoke(value); + SetExpression?.Invoke(DataBinding.LayerProperty.CurrentValue, value); } /// diff --git a/src/Artemis.Core/Models/Profile/DataBindings/Converters/IntDataBindingConverter.cs b/src/Artemis.Core/Models/Profile/DataBindings/Converters/IntDataBindingConverter.cs index 2bd6bd483..70daff304 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/Converters/IntDataBindingConverter.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/Converters/IntDataBindingConverter.cs @@ -41,7 +41,7 @@ namespace Artemis.Core /// public override void ApplyValue(int value) { - SetExpression?.Invoke(value); + SetExpression?.Invoke(DataBinding.LayerProperty.CurrentValue, value); } /// diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs index 7e5240820..d584a802a 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using System.Reflection; using Artemis.Core.DataModelExpansions; using Artemis.Storage.Entities.Profile.DataBindings; @@ -14,9 +13,9 @@ namespace Artemis.Core private readonly List> _modifiers = new List>(); private TProperty _currentValue; + private bool _disposed; private TimeSpan _easingProgress; private TProperty _previousValue; - private bool _disposed; internal DataBinding(DataBindingRegistration dataBindingRegistration) { @@ -53,11 +52,6 @@ namespace Artemis.Core /// public DataBindingConverter Converter { get; private set; } - /// - /// Gets the property on the this data binding targets - /// - public PropertyInfo TargetProperty { get; private set; } - /// /// Gets the currently used instance of the data model that contains the source of the data binding /// @@ -118,8 +112,21 @@ namespace Artemis.Core if (Converter == null) return; - var value = GetValue(Converter.GetValue()); - Converter.ApplyValue(GetValue(value)); + var converterValue = Converter.GetValue(); + var value = GetValue(converterValue); + Converter.ApplyValue(value); + } + + /// + public void Dispose() + { + _disposed = true; + + DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded; + DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved; + + foreach (var dataBindingModifier in Modifiers) + dataBindingModifier.Dispose(); } /// @@ -218,7 +225,7 @@ namespace Artemis.Core /// public Type GetTargetType() { - return TargetProperty.PropertyType; + return Registration.PropertyExpression.ReturnType; } /// @@ -257,7 +264,6 @@ namespace Artemis.Core Converter = dataBindingRegistration?.Converter; Registration = dataBindingRegistration; - TargetProperty = dataBindingRegistration?.Property; if (GetTargetType().IsValueType) { @@ -305,7 +311,7 @@ namespace Artemis.Core if (_disposed) throw new ObjectDisposedException("DataBinding"); // General - var registration = LayerProperty.GetDataBindingRegistration(Entity.TargetProperty); + var registration = LayerProperty.GetDataBindingRegistration(Entity.TargetExpression); ApplyRegistration(registration); Mode = (DataBindingMode) Entity.DataBindingMode; @@ -329,7 +335,7 @@ namespace Artemis.Core LayerProperty.Entity.DataBindingEntities.Add(Entity); // General - Entity.TargetProperty = TargetProperty?.Name; + Entity.TargetExpression = Registration.PropertyExpression.ToString(); Entity.DataBindingMode = (int) Mode; Entity.EasingTime = EasingTime; Entity.EasingFunction = (int) EasingFunction; @@ -340,7 +346,7 @@ namespace Artemis.Core Entity.SourceDataModelGuid = SourceDataModel.PluginInfo.Guid; Entity.SourcePropertyPath = SourcePropertyPath; } - + // Modifiers Entity.Modifiers.Clear(); foreach (var dataBindingModifier in Modifiers) @@ -381,18 +387,6 @@ namespace Artemis.Core } #endregion - - /// - public void Dispose() - { - _disposed = true; - - DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded; - DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved; - - foreach (var dataBindingModifier in Modifiers) - dataBindingModifier.Dispose(); - } } /// diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs index e5d3f3b13..fbe7642fc 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingConverter.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Linq.Expressions; +using System.Reflection; namespace Artemis.Core { @@ -18,7 +19,7 @@ namespace Artemis.Core /// /// A dynamically compiled setter pointing to the data bound property /// - public Action SetExpression { get; private set; } + public Action SetExpression { get; private set; } /// /// Gets the data binding this converter is applied to @@ -81,19 +82,34 @@ namespace Artemis.Core OnInitialized(); } - private Action CreateValueSetter() + private Action CreateValueSetter() { - if (DataBinding.TargetProperty?.DeclaringType == null) - return null; + MethodInfo setterMethod = null; + + if (DataBinding.Registration.Member != null) + { + if (DataBinding.Registration.Member is PropertyInfo propertyInfo) + setterMethod = propertyInfo.GetSetMethod(); + } - var setterMethod = DataBinding.TargetProperty.GetSetMethod(); if (setterMethod == null) return null; + var parameter = Expression.Parameter(typeof(TLayerProperty), "currentValue"); var propertyValue = Expression.Parameter(typeof(TProperty), "propertyValue"); - var body = DataBinding.Registration.PropertyExpression; - var lambda = Expression.Lambda>(body, propertyValue); + + var memberAccess = Expression.MakeMemberAccess(parameter, DataBinding.Registration.Member); + var assignment = Expression.Assign(memberAccess, propertyValue); + var lambda = Expression.Lambda>(assignment, parameter, propertyValue); + + if (typeof(TLayerProperty).IsValueType) + { + // var layerProperty = Expression.Constant(DataBinding.LayerProperty); + // var layerPropertyMemberAccess = Expression.MakeMemberAccess(layerProperty, DataBinding.LayerProperty.GetType().GetMember(nameof(DataBinding.LayerProperty.CurrentValue))[0]); + // var assingment = Expression.Assign() + } + return lambda.Compile(); } } diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingModifier.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingModifier.cs index bd25a62cf..05e126771 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingModifier.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingModifier.cs @@ -175,7 +175,7 @@ namespace Artemis.Core return; } - var targetType = DataBinding.TargetProperty.PropertyType; + var targetType = DataBinding.GetTargetType(); if (!modifierType.SupportsType(targetType)) { throw new ArtemisCoreException($"Cannot apply modifier type {modifierType.GetType().Name} to this modifier because " + @@ -227,7 +227,7 @@ namespace Artemis.Core ParameterDataModel = null; ParameterPropertyPath = null; - var targetType = DataBinding.TargetProperty.PropertyType; + var targetType = DataBinding.GetTargetType(); // If not null ensure the types match and if not, convert it if (staticValue != null && staticValue.GetType() == targetType) @@ -269,7 +269,7 @@ namespace Artemis.Core else if (ParameterType == ProfileRightSideType.Static && Entity.ParameterStaticValue != null && ParameterStaticValue == null) { // Use the target type so JSON.NET has a better idea what to do - var targetType = DataBinding.TargetProperty.PropertyType; + var targetType = DataBinding.GetTargetType(); object staticValue; try diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs index dba41727f..d7366ca63 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs @@ -8,13 +8,16 @@ namespace Artemis.Core /// public class DataBindingRegistration : IDataBindingRegistration { - internal DataBindingRegistration(LayerProperty layerProperty, - DataBindingConverter converter, + internal DataBindingRegistration(LayerProperty layerProperty, + DataBindingConverter converter, Expression> propertyExpression) { 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; } /// @@ -28,15 +31,16 @@ namespace Artemis.Core public DataBindingConverter Converter { get; } /// - /// Gets the expression that that accesses the property + /// Gets the expression that that accesses the property /// public Expression> PropertyExpression { get; } /// - /// Gets the registered property + /// Gets the member the targets + /// null if the is not a member expression /// - public PropertyInfo Property { get; } - + public MemberInfo Member { get; } + /// /// Gets the data binding created using this registration /// @@ -48,7 +52,7 @@ namespace Artemis.Core if (DataBinding != null) return DataBinding; - var dataBinding = LayerProperty.Entity.DataBindingEntities.FirstOrDefault(e => e.TargetProperty == PropertyExpression.ToString()); + var dataBinding = LayerProperty.Entity.DataBindingEntities.FirstOrDefault(e => e.TargetExpression == PropertyExpression.ToString()); if (dataBinding == null) return null; diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs index 4491b8143..cb495ca70 100644 --- a/src/Artemis.Core/Models/Profile/Folder.cs +++ b/src/Artemis.Core/Models/Profile/Folder.cs @@ -193,7 +193,7 @@ namespace Artemis.Core canvas.Restore(); } - + /// public override void AddChild(ProfileElement child, int? order = null) { @@ -242,17 +242,13 @@ namespace Artemis.Core protected override void Dispose(bool disposing) { - if (!disposing) - return; - _disposed = true; - foreach (var baseLayerEffect in LayerEffects) - baseLayerEffect.Dispose(); foreach (var profileElement in Children) profileElement.Dispose(); _folderBitmap?.Dispose(); + base.Dispose(disposing); } internal override void Load() @@ -290,10 +286,6 @@ namespace Artemis.Core FolderEntity.ExpandedPropertyGroups.Clear(); FolderEntity.ExpandedPropertyGroups.AddRange(_expandedPropertyGroups); - // Conditions - RenderElementEntity.RootDisplayCondition = DisplayConditionGroup?.Entity; - DisplayConditionGroup?.Save(); - SaveRenderElement(); } diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index ac2c141fc..7fdad81f5 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -124,20 +124,16 @@ namespace Artemis.Core /// protected override void Dispose(bool disposing) { - if (!disposing) - return; - _disposed = true; // Brush first in case it depends on any of the other disposables during it's own disposal _layerBrush?.Dispose(); - foreach (var baseLayerEffect in LayerEffects) - baseLayerEffect.Dispose(); - _general?.Dispose(); _layerBitmap?.Dispose(); _transform?.Dispose(); + + base.Dispose(disposing); } #endregion @@ -210,10 +206,6 @@ namespace Artemis.Core LayerEntity.Leds.Add(ledEntity); } - // Conditions - RenderElementEntity.RootDisplayCondition = DisplayConditionGroup?.Entity; - DisplayConditionGroup?.Save(); - SaveRenderElement(); } @@ -691,14 +683,8 @@ namespace Artemis.Core // Ensure the brush reference matches the brush var current = General.BrushReference.CurrentValue; - if (current.BrushPluginGuid != descriptor.LayerBrushProvider.PluginInfo.Guid || current.BrushType != descriptor.LayerBrushType.Name) - { - General.BrushReference.CurrentValue = new LayerBrushReference - { - BrushPluginGuid = descriptor.LayerBrushProvider.PluginInfo.Guid, - BrushType = descriptor.LayerBrushType.Name - }; - } + if (!descriptor.MatchesLayerBrushReference(current)) + General.BrushReference.CurrentValue = new LayerBrushReference(descriptor); ActivateLayerBrush(); } @@ -746,7 +732,7 @@ namespace Artemis.Core private void LayerBrushStoreOnLayerBrushRemoved(object sender, LayerBrushStoreEvent e) { - if (LayerBrush.Descriptor == e.Registration.LayerBrushDescriptor) + if (LayerBrush?.Descriptor == e.Registration.LayerBrushDescriptor) DeactivateLayerBrush(); } diff --git a/src/Artemis.Core/Models/Profile/LayerBrushReference.cs b/src/Artemis.Core/Models/Profile/LayerBrushReference.cs index 588fac723..bcad54663 100644 --- a/src/Artemis.Core/Models/Profile/LayerBrushReference.cs +++ b/src/Artemis.Core/Models/Profile/LayerBrushReference.cs @@ -17,5 +17,15 @@ namespace Artemis.Core /// The full type name of the brush descriptor /// public string BrushType { get; set; } + + public LayerBrushReference() + { + } + + public LayerBrushReference(LayerBrushDescriptor descriptor) + { + BrushPluginGuid = descriptor.LayerBrushProvider.PluginInfo.Guid; + BrushType = descriptor.LayerBrushType.Name; + } } } \ 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 index 9624bd576..909255cb5 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs @@ -26,10 +26,5 @@ namespace Artemis.Core /// Returns a list off all data binding registrations /// List GetAllDataBindingRegistrations(); - - /// - /// Gets or sets whether the property is hidden in the UI - /// - bool IsHidden { get; set; } } } \ 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 2737daf91..01ccfc4c7 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs @@ -61,7 +61,9 @@ namespace Artemis.Core private bool _isHidden; - /// + /// + /// Gets or sets whether the property is hidden in the UI + /// public bool IsHidden { get => _isHidden; @@ -318,13 +320,13 @@ namespace Artemis.Core /// public bool DataBindingsSupported { get; protected internal set; } = true; - public DataBindingRegistration GetDataBindingRegistration(string propertyName) + public DataBindingRegistration GetDataBindingRegistration(string expression) { if (_disposed) throw new ObjectDisposedException("LayerProperty"); var match = _dataBindingRegistrations.FirstOrDefault(r => r is DataBindingRegistration registration && - registration.Property.Name == propertyName); + registration.PropertyExpression.ToString() == expression); return (DataBindingRegistration) match; } diff --git a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs index 1b4951458..a355747ce 100644 --- a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs +++ b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs @@ -237,6 +237,10 @@ namespace Artemis.Core if (instance == null) throw new ArtemisPluginException($"Failed to create instance of layer property group at {path + propertyInfo.Name}"); + // Ensure the description has a name, if not this is a good point to set it based on the property info + if (string.IsNullOrWhiteSpace(propertyGroupDescription.Name)) + propertyGroupDescription.Name = propertyInfo.Name.Humanize(); + instance.Parent = this; instance.GroupDescription = propertyGroupDescription; instance.LayerBrush = LayerBrush; diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs index 4c735e94d..e225f6b57 100644 --- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs @@ -32,6 +32,10 @@ namespace Artemis.Core DisplayContinuously = RenderElementEntity.DisplayContinuously; AlwaysFinishTimeline = RenderElementEntity.AlwaysFinishTimeline; + DisplayConditionGroup = RenderElementEntity.RootDisplayCondition != null + ? new DisplayConditionGroup(null, RenderElementEntity.RootDisplayCondition) + : new DisplayConditionGroup(null); + ActivateEffects(); } @@ -49,7 +53,7 @@ namespace Artemis.Core var layerEffectEntity = new LayerEffectEntity { Id = layerEffect.EntityId, - PluginGuid = layerEffect.PluginInfo.Guid, + PluginGuid = layerEffect.Descriptor.PlaceholderFor ?? layerEffect.PluginInfo.Guid, EffectType = layerEffect.GetEffectTypeName(), Name = layerEffect.Name, Enabled = layerEffect.Enabled, @@ -59,6 +63,10 @@ namespace Artemis.Core RenderElementEntity.LayerEffects.Add(layerEffectEntity); layerEffect.BaseProperties.ApplyToEntity(); } + + // Conditions + RenderElementEntity.RootDisplayCondition = DisplayConditionGroup?.Entity; + DisplayConditionGroup?.Save(); } #region Properties @@ -204,7 +212,7 @@ namespace Artemis.Core return (TimelinePosition - oldPosition).TotalSeconds; } - + /// /// Overrides the progress of the element /// @@ -238,6 +246,8 @@ namespace Artemis.Core Order = LayerEffects.Count + 1 }; descriptor.CreateInstance(this, entity); + + OrderEffects(); OnLayerEffectsUpdated(); } @@ -254,6 +264,12 @@ namespace Artemis.Core effect.Dispose(); // Update the order on the remaining effects + OrderEffects(); + OnLayerEffectsUpdated(); + } + + private void OrderEffects() + { var index = 0; foreach (var baseLayerEffect in LayerEffects.OrderBy(e => e.Order)) { @@ -261,7 +277,7 @@ namespace Artemis.Core index++; } - OnLayerEffectsUpdated(); + _layerEffects.Sort((a, b) => a.Order.CompareTo(b.Order)); } internal void ActivateEffects() @@ -270,7 +286,7 @@ namespace Artemis.Core { // If there is a non-placeholder existing effect, skip this entity var existing = _layerEffects.FirstOrDefault(e => e.EntityId == layerEffectEntity.Id); - if (existing != null && !existing.Descriptor.IsPlaceHolder) + if (existing != null && existing.Descriptor.PlaceholderFor == null) continue; var descriptor = LayerEffectStore.Get(layerEffectEntity.PluginGuid, layerEffectEntity.EffectType)?.LayerEffectDescriptor; @@ -282,37 +298,42 @@ namespace Artemis.Core _layerEffects.Remove(existing); existing.Dispose(); } + // Create an instance with the descriptor descriptor.CreateInstance(this, layerEffectEntity); } else if (existing == null) { // If no descriptor was found and there was no existing placeholder, create a placeholder - descriptor = PlaceholderLayerEffectDescriptor.Create(); + descriptor = PlaceholderLayerEffectDescriptor.Create(layerEffectEntity.PluginGuid); descriptor.CreateInstance(this, layerEffectEntity); } } + + OrderEffects(); } internal void ActivateLayerEffect(BaseLayerEffect layerEffect) { _layerEffects.Add(layerEffect); - - // Update the order on the effects - var index = 0; - foreach (var baseLayerEffect in LayerEffects.OrderBy(e => e.Order)) - { - baseLayerEffect.Order = Order = index + 1; - index++; - } - OnLayerEffectsUpdated(); } - private void LayerEffectStoreOnLayerEffectRemoved(object? sender, LayerEffectStoreEvent e) + private void LayerEffectStoreOnLayerEffectRemoved(object sender, LayerEffectStoreEvent e) { - throw new NotImplementedException(); + // If effects provided by the plugin are on the element, replace them with placeholders + var pluginEffects = _layerEffects.Where(ef => ef.Descriptor.LayerEffectProvider != null && + ef.PluginInfo.Guid == e.Registration.Plugin.PluginInfo.Guid).ToList(); + foreach (var pluginEffect in pluginEffects) + { + var entity = RenderElementEntity.LayerEffects.First(en => en.Id == pluginEffect.EntityId); + _layerEffects.Remove(pluginEffect); + pluginEffect.Dispose(); + + var descriptor = PlaceholderLayerEffectDescriptor.Create(pluginEffect.PluginInfo.Guid); + descriptor.CreateInstance(this, entity); + } } private void LayerEffectStoreOnLayerEffectAdded(object sender, LayerEffectStoreEvent e) @@ -359,6 +380,21 @@ namespace Artemis.Core #endregion + #region IDisposable + + protected override void Dispose(bool disposing) + { + LayerEffectStore.LayerEffectAdded -= LayerEffectStoreOnLayerEffectAdded; + LayerEffectStore.LayerEffectRemoved -= LayerEffectStoreOnLayerEffectRemoved; + + foreach (var baseLayerEffect in LayerEffects) + baseLayerEffect.Dispose(); + + base.Dispose(disposing); + } + + #endregion + #region Events public event EventHandler LayerEffectsUpdated; diff --git a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushDescriptor.cs b/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushDescriptor.cs index f0fd29539..c03948109 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushDescriptor.cs +++ b/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushDescriptor.cs @@ -61,5 +61,12 @@ namespace Artemis.Core.LayerBrushes layer.LayerBrush = brush; layer.OnLayerBrushUpdated(); } + + public bool MatchesLayerBrushReference(LayerBrushReference reference) + { + if (reference == null) + return false; + return LayerBrushProvider.PluginInfo.Guid == reference.BrushPluginGuid && LayerBrushType.Name == reference.BrushType; + } } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs b/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs index 0607a7f24..e4ec96c9e 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs +++ b/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs @@ -95,7 +95,7 @@ namespace Artemis.Core.LayerEffects /// /// Gets the plugin info that defined this effect /// - public PluginInfo PluginInfo => Descriptor.LayerEffectProvider.PluginInfo; + public PluginInfo PluginInfo => Descriptor.LayerEffectProvider?.PluginInfo; /// /// Gets a reference to the layer property group without knowing it's type @@ -108,7 +108,7 @@ namespace Artemis.Core.LayerEffects public void Dispose() { DisableLayerEffect(); - BaseProperties.Dispose(); + BaseProperties?.Dispose(); } /// diff --git a/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs b/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs index 21cb29d7d..50ffaa310 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs +++ b/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using Artemis.Core.LayerEffects.Placeholder; using Artemis.Core.Services; using Artemis.Storage.Entities.Profile; using Ninject; @@ -47,9 +48,9 @@ namespace Artemis.Core.LayerEffects public LayerEffectProvider LayerEffectProvider { get; } /// - /// Gets a boolean indicating if this descriptor is a placeholder for a missing plugin + /// Gets the GUID this descriptor is acting as a placeholder for. If null, this descriptor is not a placeholder /// - public bool IsPlaceHolder { get; internal set; } + public Guid? PlaceholderFor { get; internal set; } /// /// Creates an instance of the described effect and applies it to the render element @@ -60,7 +61,7 @@ namespace Artemis.Core.LayerEffects if (renderElement.LayerEffects.Any(e => e.EntityId == entity.Id)) return; - if (IsPlaceHolder) + if (PlaceholderFor != null) { CreatePlaceHolderInstance(renderElement, entity); return; @@ -82,7 +83,8 @@ namespace Artemis.Core.LayerEffects private void CreatePlaceHolderInstance(RenderProfileElement renderElement, LayerEffectEntity entity) { - var effect = new PlaceholderLayerEffect(entity) {ProfileElement = renderElement, Descriptor = this}; + var effect = new PlaceholderLayerEffect(entity, PlaceholderFor.Value) {ProfileElement = renderElement, Descriptor = this}; + effect.Initialize(); renderElement.ActivateLayerEffect(effect); } } diff --git a/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffect.cs b/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffect.cs index 067417d0d..995b244e9 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffect.cs +++ b/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffect.cs @@ -1,17 +1,19 @@ -using Artemis.Storage.Entities.Profile; +using System; +using Artemis.Storage.Entities.Profile; using SkiaSharp; -namespace Artemis.Core.LayerEffects +namespace Artemis.Core.LayerEffects.Placeholder { /// /// Represents a layer effect that could not be loaded due to a missing plugin /// - public class PlaceholderLayerEffect : BaseLayerEffect + internal class PlaceholderLayerEffect : LayerEffect { - internal PlaceholderLayerEffect(LayerEffectEntity originalEntity) + internal PlaceholderLayerEffect(LayerEffectEntity originalEntity, Guid placeholderFor) { OriginalEntity = originalEntity; - + PlaceholderFor = placeholderFor; + EntityId = OriginalEntity.Id; Order = OriginalEntity.Order; Name = OriginalEntity.Name; @@ -20,6 +22,7 @@ namespace Artemis.Core.LayerEffects } internal LayerEffectEntity OriginalEntity { get; } + public Guid PlaceholderFor { get; } /// public override void EnableLayerEffect() @@ -50,8 +53,22 @@ namespace Artemis.Core.LayerEffects { return OriginalEntity.EffectType; } + } - internal override void Initialize() + /// + /// This is in place so that the UI has something to show + /// + internal class PlaceholderProperties : LayerPropertyGroup + { + protected override void PopulateDefaults() + { + } + + protected override void EnableProperties() + { + } + + protected override void DisableProperties() { } } diff --git a/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffectDescriptor.cs b/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffectDescriptor.cs index b34583a0f..63e738c37 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffectDescriptor.cs +++ b/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffectDescriptor.cs @@ -1,10 +1,16 @@ -namespace Artemis.Core.LayerEffects.Placeholder +using System; + +namespace Artemis.Core.LayerEffects.Placeholder { internal static class PlaceholderLayerEffectDescriptor { - public static LayerEffectDescriptor Create() + public static LayerEffectDescriptor Create(Guid missingPluginGuid) { - var descriptor = new LayerEffectDescriptor("Missing effect", "This effect could not be loaded", "FileQuestion", null, null) {IsPlaceHolder = true}; + var descriptor = new LayerEffectDescriptor("Missing effect", "This effect could not be loaded", "FileQuestion", null, Constants.EffectPlaceholderPlugin) + { + PlaceholderFor = missingPluginGuid + }; + return descriptor; } } diff --git a/src/Artemis.Core/Plugins/Plugin.cs b/src/Artemis.Core/Plugins/Plugin.cs index d8f260b1b..dd6c0542f 100644 --- a/src/Artemis.Core/Plugins/Plugin.cs +++ b/src/Artemis.Core/Plugins/Plugin.cs @@ -16,7 +16,7 @@ namespace Artemis.Core /// /// Gets whether the plugin is enabled /// - public bool Enabled { get; private set; } + public bool Enabled { get; internal set; } /// /// Gets or sets a configuration dialog for this plugin that is accessible in the UI under Settings > Plugins diff --git a/src/Artemis.Core/Services/Registration/Interfaces/ILayerBrushService.cs b/src/Artemis.Core/Services/Registration/Interfaces/ILayerBrushService.cs index 8bc6a62e0..a8e576312 100644 --- a/src/Artemis.Core/Services/Registration/Interfaces/ILayerBrushService.cs +++ b/src/Artemis.Core/Services/Registration/Interfaces/ILayerBrushService.cs @@ -22,5 +22,10 @@ namespace Artemis.Core.Services /// Returns a list of all registered layer brush descriptors /// List GetLayerBrushes(); + + /// + /// Returns the descriptor of the default layer brush + /// + LayerBrushDescriptor GetDefaultLayerBrush(); } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Registration/LayerBrushService.cs b/src/Artemis.Core/Services/Registration/LayerBrushService.cs index b46d24143..8a3a69e55 100644 --- a/src/Artemis.Core/Services/Registration/LayerBrushService.cs +++ b/src/Artemis.Core/Services/Registration/LayerBrushService.cs @@ -7,6 +7,13 @@ namespace Artemis.Core.Services { internal class LayerBrushService : ILayerBrushService { + private readonly ISettingsService _settingsService; + + public LayerBrushService(ISettingsService settingsService) + { + _settingsService = settingsService; + } + public LayerBrushRegistration RegisterLayerBrush(LayerBrushDescriptor descriptor) { if (descriptor == null) @@ -26,5 +33,16 @@ namespace Artemis.Core.Services { return LayerBrushStore.GetAll().Select(r => r.LayerBrushDescriptor).ToList(); } + + public LayerBrushDescriptor GetDefaultLayerBrush() + { + var defaultReference = _settingsService.GetSetting("ProfileEditor.DefaultLayerBrushDescriptor", new LayerBrushReference + { + BrushPluginGuid = Guid.Parse("92a9d6ba-6f7a-4937-94d5-c1d715b4141a"), + BrushType = "ColorBrush" + }); + + return LayerBrushStore.Get(defaultReference.Value.BrushPluginGuid, defaultReference.Value.BrushType)?.LayerBrushDescriptor; + } } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index f51c51a69..f31724d93 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -2,8 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Artemis.Core.LayerBrushes; -using Artemis.Core.LayerEffects; using Artemis.Core.Modules; using Artemis.Storage.Entities.Profile; using Artemis.Storage.Repositories.Interfaces; @@ -19,7 +17,12 @@ namespace Artemis.Core.Services private readonly IProfileRepository _profileRepository; private readonly ISurfaceService _surfaceService; - internal ProfileService(ILogger logger, IPluginService pluginService, ISurfaceService surfaceService, IProfileRepository profileRepository) + internal ProfileService(ILogger logger, + IPluginService pluginService, + ISurfaceService surfaceService, + IConditionOperatorService conditionOperatorService, + IDataBindingService dataBindingService, + IProfileRepository profileRepository) { _logger = logger; _pluginService = pluginService; @@ -261,7 +264,7 @@ namespace Artemis.Core.Services _profileRepository.Save(profileEntity); } } - + /// /// Populates all missing LEDs on all currently active profiles /// @@ -272,7 +275,7 @@ namespace Artemis.Core.Services foreach (var profileModule in profileModules.Where(p => p.ActiveProfile != null).ToList()) profileModule.ActiveProfile.PopulateLeds(surface); } - + #region Event handlers private void OnActiveSurfaceConfigurationSelected(object sender, SurfaceConfigurationEventArgs e) @@ -285,7 +288,7 @@ namespace Artemis.Core.Services if (e.Surface.IsActive) ActiveProfilesPopulateLeds(e.Surface); } - + #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Utilities/CorePlugin.cs b/src/Artemis.Core/Utilities/CorePlugin.cs new file mode 100644 index 000000000..8a87a9028 --- /dev/null +++ b/src/Artemis.Core/Utilities/CorePlugin.cs @@ -0,0 +1,40 @@ +using Artemis.Core.LayerEffects; + +namespace Artemis.Core +{ + /// + /// An empty plugin used by + /// + internal class CorePlugin : Plugin + { + public CorePlugin() + { + Constants.CorePluginInfo.Instance = this; + Enabled = true; + } + + public override void EnablePlugin() + { + } + + public override void DisablePlugin() + { + } + } + + internal class EffectPlaceholderPlugin : LayerEffectProvider + { + public EffectPlaceholderPlugin() + { + Enabled = true; + } + + public override void EnablePlugin() + { + } + + public override void DisablePlugin() + { + } + } +} \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingEntity.cs b/src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingEntity.cs index 761acbeee..14011e683 100644 --- a/src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingEntity.cs @@ -10,7 +10,7 @@ namespace Artemis.Storage.Entities.Profile.DataBindings Modifiers = new List(); } - public string TargetProperty { get; set; } + public string TargetExpression { get; set; } public Guid? SourceDataModelGuid { get; set; } public string SourcePropertyPath { get; set; } public int DataBindingMode { get; set; } diff --git a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs index 7fbb05c43..cd50678b0 100644 --- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -69,12 +69,6 @@ namespace Artemis.UI.Ninject.Factories DisplayConditionListPredicateViewModel DisplayConditionListPredicateViewModel(DisplayConditionListPredicate displayConditionListPredicate); } - public interface IDataBindingsVmFactory : IVmFactory - { - IDataBindingViewModel DataBindingViewModel(IDataBindingRegistration registration); - DataBindingModifierViewModel DataBindingModifierViewModel(DataBindingModifier modifier); - } - public interface ILayerPropertyVmFactory : IVmFactory { LayerPropertyViewModel LayerPropertyViewModel(ILayerProperty layerProperty); @@ -89,6 +83,12 @@ namespace Artemis.UI.Ninject.Factories TimelineSegmentViewModel TimelineSegmentViewModel(SegmentViewModelType segment, BindableCollection layerPropertyGroups); } + public interface IDataBindingsVmFactory + { + IDataBindingViewModel DataBindingViewModel(IDataBindingRegistration registration); + DataBindingModifierViewModel DataBindingModifierViewModel(DataBindingModifier modifier); + } + public interface IPropertyVmFactory { ITreePropertyViewModel TreePropertyViewModel(ILayerProperty layerProperty, LayerPropertyViewModel layerPropertyViewModel); diff --git a/src/Artemis.UI/Ninject/InstanceProviders/DataBindingsViewModelInstanceProvider.cs b/src/Artemis.UI/Ninject/InstanceProviders/DataBindingsViewModelInstanceProvider.cs index 5df778f89..8a8e829fe 100644 --- a/src/Artemis.UI/Ninject/InstanceProviders/DataBindingsViewModelInstanceProvider.cs +++ b/src/Artemis.UI/Ninject/InstanceProviders/DataBindingsViewModelInstanceProvider.cs @@ -1,5 +1,7 @@ using System; using System.Reflection; +using Artemis.Core; +using Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings; using Ninject.Extensions.Factory; namespace Artemis.UI.Ninject.InstanceProviders @@ -8,7 +10,17 @@ namespace Artemis.UI.Ninject.InstanceProviders { protected override Type GetType(MethodInfo methodInfo, object[] arguments) { - return base.GetType(methodInfo, arguments); + if (methodInfo.ReturnType != typeof(IDataBindingViewModel)) + return base.GetType(methodInfo, arguments); + + // Find LayerProperty type + var descriptionPropertyType = arguments[0].GetType(); + while (descriptionPropertyType != null && (!descriptionPropertyType.IsGenericType || descriptionPropertyType.GetGenericTypeDefinition() != typeof(DataBindingRegistration<,>))) + descriptionPropertyType = descriptionPropertyType.BaseType; + if (descriptionPropertyType == null) + return base.GetType(methodInfo, arguments); + + return typeof(DataBindingViewModel<,>).MakeGenericType(descriptionPropertyType.GetGenericArguments()); } } } \ No newline at end of file diff --git a/src/Artemis.UI/Ninject/UiModule.cs b/src/Artemis.UI/Ninject/UiModule.cs index 1da66b61c..3d807d1d9 100644 --- a/src/Artemis.UI/Ninject/UiModule.cs +++ b/src/Artemis.UI/Ninject/UiModule.cs @@ -50,6 +50,7 @@ namespace Artemis.UI.Ninject .BindToFactory(); }); + Kernel.Bind().ToFactory(() => new DataBindingsViewModelInstanceProvider()); Kernel.Bind().ToFactory(() => new LayerPropertyViewModelInstanceProvider()); // Bind profile editor VMs diff --git a/src/Artemis.UI/PropertyInput/BrushPropertyInputView.xaml b/src/Artemis.UI/PropertyInput/BrushPropertyInputView.xaml index c698304b8..820bbbdc4 100644 --- a/src/Artemis.UI/PropertyInput/BrushPropertyInputView.xaml +++ b/src/Artemis.UI/PropertyInput/BrushPropertyInputView.xaml @@ -11,28 +11,11 @@ d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance {x:Type propertyInput:BrushPropertyInputViewModel}}"> - - - - - - - - - - - - - - - - - - - - - - + + + + + @@ -46,8 +29,8 @@ ItemsSource="{Binding Path=Descriptors}" SelectedValue="{Binding Path=SelectedDescriptor}" ItemTemplateSelector="{dataTemplateSelectors:ComboBoxTemplateSelector - SelectedItemTemplate={StaticResource SimpleTemplate}, - DropdownItemsTemplate={StaticResource ExtendedTemplate}}" /> + SelectedItemTemplate={StaticResource SimpleLayerBrushDescriptorTemplate}, + DropdownItemsTemplate={StaticResource ExtendedLayerBrushDescriptorTemplate}}" /> \ No newline at end of file diff --git a/src/Artemis.UI/PropertyInput/BrushPropertyInputViewModel.cs b/src/Artemis.UI/PropertyInput/BrushPropertyInputViewModel.cs index 2222ecf81..61c512175 100644 --- a/src/Artemis.UI/PropertyInput/BrushPropertyInputViewModel.cs +++ b/src/Artemis.UI/PropertyInput/BrushPropertyInputViewModel.cs @@ -5,16 +5,17 @@ using Artemis.Core.LayerBrushes; using Artemis.Core.Services; using Artemis.UI.Shared; using Artemis.UI.Shared.Services; +using Stylet; namespace Artemis.UI.PropertyInput { public class BrushPropertyInputViewModel : PropertyInputViewModel { private readonly IPluginService _pluginService; - private List _descriptors; + private BindableCollection _descriptors; public BrushPropertyInputViewModel(LayerProperty layerProperty, - IProfileEditorService profileEditorService, + IProfileEditorService profileEditorService, IPluginService pluginService) : base(layerProperty, profileEditorService) { _pluginService = pluginService; @@ -24,7 +25,7 @@ namespace Artemis.UI.PropertyInput UpdateEnumValues(); } - public List Descriptors + public BindableCollection Descriptors { get => _descriptors; set => SetAndNotify(ref _descriptors, value); @@ -32,14 +33,14 @@ namespace Artemis.UI.PropertyInput public LayerBrushDescriptor SelectedDescriptor { - get => Descriptors.FirstOrDefault(d => d.LayerBrushProvider.PluginInfo.Guid == InputValue?.BrushPluginGuid && d.LayerBrushType.Name == InputValue?.BrushType); + get => Descriptors.FirstOrDefault(d => d.MatchesLayerBrushReference(InputValue)); set => SetBrushByDescriptor(value); } public void UpdateEnumValues() { var layerBrushProviders = _pluginService.GetPluginsOfType(); - Descriptors = layerBrushProviders.SelectMany(l => l.LayerBrushDescriptors).ToList(); + Descriptors = new BindableCollection(layerBrushProviders.SelectMany(l => l.LayerBrushDescriptors)); NotifyOfPropertyChange(nameof(SelectedDescriptor)); } @@ -53,13 +54,13 @@ namespace Artemis.UI.PropertyInput protected override void OnInputValueApplied() { - if (LayerProperty.ProfileElement is Layer layer) + if (LayerProperty.ProfileElement is Layer layer) layer.ChangeLayerBrush(SelectedDescriptor); } private void SetBrushByDescriptor(LayerBrushDescriptor value) { - InputValue = new LayerBrushReference {BrushPluginGuid = value.LayerBrushProvider.PluginInfo.Guid, BrushType = value.LayerBrushType.Name}; + InputValue = new LayerBrushReference(value); } private void PluginServiceOnPluginLoaded(object sender, PluginEventArgs e) diff --git a/src/Artemis.UI/ResourceDictionaries/LayerBrushDescriptors.xaml b/src/Artemis.UI/ResourceDictionaries/LayerBrushDescriptors.xaml new file mode 100644 index 000000000..059b43857 --- /dev/null +++ b/src/Artemis.UI/ResourceDictionaries/LayerBrushDescriptors.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs index e2d3a7a04..5392d0f52 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs @@ -11,8 +11,6 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions { private readonly IDisplayConditionsVmFactory _displayConditionsVmFactory; private readonly IProfileEditorService _profileEditorService; - private bool _alwaysFinishTimeline; - private bool _displayContinuously; private RenderProfileElement _renderProfileElement; private int _transitionerIndex; @@ -37,20 +35,22 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions public bool DisplayContinuously { - get => _displayContinuously; + get => RenderProfileElement?.DisplayContinuously ?? false; set { - if (!SetAndNotify(ref _displayContinuously, value)) return; + if (RenderProfileElement == null || RenderProfileElement.DisplayContinuously == value) return; + RenderProfileElement.DisplayContinuously = value; _profileEditorService.UpdateSelectedProfileElement(); } } public bool AlwaysFinishTimeline { - get => _alwaysFinishTimeline; + get => RenderProfileElement?.AlwaysFinishTimeline ?? false; set { - if (!SetAndNotify(ref _alwaysFinishTimeline, value)) return; + if (RenderProfileElement == null || RenderProfileElement.AlwaysFinishTimeline == value) return; + RenderProfileElement.AlwaysFinishTimeline = value; _profileEditorService.UpdateSelectedProfileElement(); } } @@ -70,12 +70,9 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions private void ProfileEditorServiceOnProfileElementSelected(object sender, RenderProfileElementEventArgs e) { RenderProfileElement = e.RenderProfileElement; - NotifyOfPropertyChange(nameof(ConditionBehaviourEnabled)); - - _displayContinuously = RenderProfileElement?.DisplayContinuously ?? false; NotifyOfPropertyChange(nameof(DisplayContinuously)); - _alwaysFinishTimeline = RenderProfileElement?.AlwaysFinishTimeline ?? false; NotifyOfPropertyChange(nameof(AlwaysFinishTimeline)); + NotifyOfPropertyChange(nameof(ConditionBehaviourEnabled)); if (e.RenderProfileElement == null) { diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml index 0afdbd491..779744f82 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml @@ -4,14 +4,10 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" - xmlns:converters="clr-namespace:Artemis.UI.Converters" - xmlns:utilities="clr-namespace:Artemis.UI.Utilities" xmlns:dd="urn:gong-wpf-dragdrop" xmlns:s="https://github.com/canton7/Stylet" - xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings" mc:Ignorable="d" - d:DesignHeight="450" d:DesignWidth="800" - d:DataContext="{d:DesignInstance local:DataBindingViewModel}"> + d:DesignHeight="450" d:DesignWidth="800"> diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs index dc89d11a1..0cee22ffa 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs @@ -36,7 +36,10 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings _dataModelUIService = dataModelUIService; _dataBindingsVmFactory = dataBindingsVmFactory; - DisplayName = Registration.Property.Name.ToUpper(); + if (Registration.Member != null) + DisplayName = Registration.Member.Name.ToUpper(); + else + DisplayName = Registration.LayerProperty.PropertyDescription.Name.ToUpper(); DataBindingModes = new BindableCollection(EnumUtilities.GetAllValuesAndDescriptions(typeof(DataBindingMode))); EasingViewModels = new BindableCollection(); @@ -203,7 +206,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings TargetSelectionViewModel.IsEnabled = true; TargetSelectionViewModel.PopulateSelectedPropertyViewModel(DataBinding.SourceDataModel, DataBinding.SourcePropertyPath); - TargetSelectionViewModel.FilterTypes = new[] {DataBinding.TargetProperty.PropertyType}; + TargetSelectionViewModel.FilterTypes = new[] {DataBinding.GetTargetType()}; UpdateModifierViewModels(); @@ -225,11 +228,18 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings private void UpdateTestResult() { + if (DataBinding == null) + { + TestInputValue = default; + TestResultValue = default; + return; + } + var currentValue = TargetSelectionViewModel.SelectedPropertyViewModel?.GetCurrentValue(); - if (currentValue == null && Registration.Property.PropertyType.IsValueType) - currentValue = Activator.CreateInstance(Registration.Property.PropertyType); - - TestInputValue = currentValue is TProperty testInputValue ? testInputValue : default; + if (currentValue == null) + currentValue = default(TProperty); + + TestInputValue = (TProperty) Convert.ChangeType(currentValue, typeof(TProperty)); if (DataBinding != null) TestResultValue = DataBinding.GetValue(TestInputValue); else diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsView.xaml index 85def1ef5..35e1c740c 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsView.xaml @@ -6,7 +6,7 @@ xmlns:s="https://github.com/canton7/Stylet" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> - + diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs index badbda92c..b9b454236 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs @@ -9,6 +9,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings { private readonly IDataBindingsVmFactory _dataBindingsVmFactory; private readonly IProfileEditorService _profileEditorService; + private int _selectedItemIndex; public DataBindingsViewModel(IProfileEditorService profileEditorService, IDataBindingsVmFactory dataBindingsVmFactory) { @@ -19,6 +20,12 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings CreateDataBindingViewModels(); } + public int SelectedItemIndex + { + get => _selectedItemIndex; + set => SetAndNotify(ref _selectedItemIndex, value); + } + private void CreateDataBindingViewModels() { Items.Clear(); @@ -33,6 +40,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings // and creating the actual data bindings foreach (var registration in registrations) ActivateItem(_dataBindingsVmFactory.DataBindingViewModel(registration)); + + SelectedItemIndex = 0; } protected override void OnClose() diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerEffects/EffectsView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerEffects/EffectsView.xaml index 792b2a58a..2e8460ced 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerEffects/EffectsView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerEffects/EffectsView.xaml @@ -23,7 +23,7 @@ Think of things like blur, black & white but also audio visualization etc. -