From 7fff1a593f0abb6b4cc7c7578447b7161c8ab288 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Sat, 12 Sep 2020 23:11:08 +0200 Subject: [PATCH] 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; }