From 6ee06b2fc504158a06adb50b52d8cac85fed5aae Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 2 Jul 2020 20:25:20 +0200 Subject: [PATCH] Data model visualization - Moved services and VMs to UI.Shared Data model visualization - Added support for custom display VMs Data model visualization - Added framework for custom input VMs Shared UI - Made internal converters public --- .../Plugins/Abstract/LayerBrushProvider.cs | 6 +- .../Controls/ColorPicker.xaml | 2 - .../Converters/ColorToSolidColorConverter.cs | 2 +- .../Converters/ColorToStringConverter.cs | 2 +- .../Converters/SKColorToStringConverter.cs | 29 ++++ .../DataModelDisplayViewModel.cs | 39 +++++ .../DataModelInputViewModel.cs | 28 ++++ .../DataModelVisualizationRegistration.cs | 53 +++++++ .../Shared}/DataModelListViewModel.cs | 12 +- .../Shared/DataModelPropertyViewModel.cs | 59 +++++++ .../Shared}/DataModelViewModel.cs | 12 +- .../DataModelVisualizationViewModel.cs | 33 ++-- .../PropertyInputRegistration.cs | 9 +- .../PropertyInput/PropertyInputViewModel.cs | 40 ++++- .../Services/DataModelVisualizationService.cs | 144 ++++++++++++++++++ .../Interfaces/IProfileEditorService.cs | 10 +- .../Services/ProfileEditorService.cs | 8 +- .../DataModelPropertyViewModel.cs | 23 --- .../SKColorDataModelDisplayView.xaml | 40 +++++ .../SKColorDataModelDisplayViewModel.cs | 9 ++ .../SKPointPropertyInputViewModel.cs | 4 - .../Debug/Tabs/DataModelDebugView.xaml | 15 +- .../Debug/Tabs/DataModelDebugViewModel.cs | 3 +- .../Services/DataModelVisualizationService.cs | 43 ------ src/Artemis.UI/Services/DebugService.cs | 17 ++- src/Artemis.UI/Services/LayerEditorService.cs | 14 +- .../ColorBrushProvider.cs | 2 +- .../RgbNetColorBrushProvider.cs | 4 +- .../NoiseBrushProvider.cs | 2 +- .../GeneralDataModel.cs | 2 + 30 files changed, 537 insertions(+), 129 deletions(-) create mode 100644 src/Artemis.UI.Shared/Converters/SKColorToStringConverter.cs create mode 100644 src/Artemis.UI.Shared/DataModelVisualization/DataModelDisplayViewModel.cs create mode 100644 src/Artemis.UI.Shared/DataModelVisualization/DataModelInputViewModel.cs create mode 100644 src/Artemis.UI.Shared/DataModelVisualization/DataModelVisualizationRegistration.cs rename src/{Artemis.UI/DataModelVisualization => Artemis.UI.Shared/DataModelVisualization/Shared}/DataModelListViewModel.cs (78%) create mode 100644 src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertyViewModel.cs rename src/{Artemis.UI/DataModelVisualization => Artemis.UI.Shared/DataModelVisualization/Shared}/DataModelViewModel.cs (70%) rename src/{Artemis.UI/DataModelVisualization => Artemis.UI.Shared/DataModelVisualization/Shared}/DataModelVisualizationViewModel.cs (75%) create mode 100644 src/Artemis.UI.Shared/Services/DataModelVisualizationService.cs delete mode 100644 src/Artemis.UI/DataModelVisualization/DataModelPropertyViewModel.cs create mode 100644 src/Artemis.UI/DataModelVisualization/SKColorDataModelDisplayView.xaml create mode 100644 src/Artemis.UI/DataModelVisualization/SKColorDataModelDisplayViewModel.cs delete mode 100644 src/Artemis.UI/Services/DataModelVisualizationService.cs diff --git a/src/Artemis.Core/Plugins/Abstract/LayerBrushProvider.cs b/src/Artemis.Core/Plugins/Abstract/LayerBrushProvider.cs index 5a87c0e1d..f44945385 100644 --- a/src/Artemis.Core/Plugins/Abstract/LayerBrushProvider.cs +++ b/src/Artemis.Core/Plugins/Abstract/LayerBrushProvider.cs @@ -22,12 +22,12 @@ namespace Artemis.Core.Plugins.Abstract } /// - /// A read-only collection of all layer brushes added with + /// A read-only collection of all layer brushes added with /// public ReadOnlyCollection LayerBrushDescriptors => _layerBrushDescriptors.AsReadOnly(); /// - /// Adds a layer brush descriptor for a given layer brush, so that it appears in the UI. + /// Registers a layer brush descriptor for a given layer brush, so that it appears in the UI. /// Note: You do not need to manually remove these on disable /// /// The type of the layer brush you wish to register @@ -37,7 +37,7 @@ namespace Artemis.Core.Plugins.Abstract /// The Material icon to display in the UI, a full reference can be found /// here /// - protected void AddLayerBrushDescriptor(string displayName, string description, string icon) where T : BaseLayerBrush + protected void RegisterLayerBrushDescriptor(string displayName, string description, string icon) where T : BaseLayerBrush { if (!Enabled) throw new ArtemisPluginException(PluginInfo, "Can only add a layer brush descriptor when the plugin is enabled"); diff --git a/src/Artemis.UI.Shared/Controls/ColorPicker.xaml b/src/Artemis.UI.Shared/Controls/ColorPicker.xaml index 250f1c1f7..07968179f 100644 --- a/src/Artemis.UI.Shared/Controls/ColorPicker.xaml +++ b/src/Artemis.UI.Shared/Controls/ColorPicker.xaml @@ -65,8 +65,6 @@ - - [ValueConversion(typeof(Color), typeof(string))] - internal class ColorToSolidColorConverter : IValueConverter + public class ColorToSolidColorConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { diff --git a/src/Artemis.UI.Shared/Converters/ColorToStringConverter.cs b/src/Artemis.UI.Shared/Converters/ColorToStringConverter.cs index e01ac78a3..6e431160b 100644 --- a/src/Artemis.UI.Shared/Converters/ColorToStringConverter.cs +++ b/src/Artemis.UI.Shared/Converters/ColorToStringConverter.cs @@ -10,7 +10,7 @@ namespace Artemis.UI.Shared.Converters /// Converts into . /// [ValueConversion(typeof(Color), typeof(string))] - internal class ColorToStringConverter : IValueConverter + public class ColorToStringConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { diff --git a/src/Artemis.UI.Shared/Converters/SKColorToStringConverter.cs b/src/Artemis.UI.Shared/Converters/SKColorToStringConverter.cs new file mode 100644 index 000000000..8bd81154b --- /dev/null +++ b/src/Artemis.UI.Shared/Converters/SKColorToStringConverter.cs @@ -0,0 +1,29 @@ +using System; +using System.Globalization; +using System.Windows.Data; +using System.Windows.Media; +using SkiaSharp; + +namespace Artemis.UI.Shared.Converters +{ + /// + /// + /// Converts into . + /// + [ValueConversion(typeof(Color), typeof(string))] + public class SKColorToStringConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value?.ToString(); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (string.IsNullOrWhiteSpace((string) value)) + return SKColor.Empty; + + return SKColor.TryParse((string)value, out var color) ? color : SKColor.Empty; + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/DataModelVisualization/DataModelDisplayViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/DataModelDisplayViewModel.cs new file mode 100644 index 000000000..0f91099df --- /dev/null +++ b/src/Artemis.UI.Shared/DataModelVisualization/DataModelDisplayViewModel.cs @@ -0,0 +1,39 @@ +using Stylet; + +namespace Artemis.UI.Shared.DataModelVisualization +{ + public abstract class DataModelDisplayViewModel : DataModelDisplayViewModel + { + private T _displayValue; + + public T DisplayValue + { + get => _displayValue; + set + { + if (!SetAndNotify(ref _displayValue, value)) return; + OnDisplayValueUpdated(); + } + } + + protected virtual void OnDisplayValueUpdated() + { + } + + internal override void UpdateValue(object model) + { + DisplayValue = model is T value ? value : default; + } + } + + /// + /// For internal use only, implement instead. + /// + public abstract class DataModelDisplayViewModel : PropertyChangedBase + { + /// + /// Prevents this type being implemented directly, implement instead. + /// + internal abstract void UpdateValue(object model); + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/DataModelVisualization/DataModelInputViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/DataModelInputViewModel.cs new file mode 100644 index 000000000..7bc781608 --- /dev/null +++ b/src/Artemis.UI.Shared/DataModelVisualization/DataModelInputViewModel.cs @@ -0,0 +1,28 @@ +using Artemis.Core.Plugins.Abstract.DataModels.Attributes; +using Stylet; + +namespace Artemis.UI.Shared.DataModelVisualization +{ + public abstract class DataModelInputViewModel : DataModelInputViewModel + { + protected DataModelInputViewModel(DataModelPropertyAttribute description) + { + Description = description; + } + + public DataModelPropertyAttribute Description { get; } + internal override object InternalGuard { get; } = null; + } + + /// + /// For internal use only, implement instead. + /// + public abstract class DataModelInputViewModel : PropertyChangedBase + { + /// + /// Prevents this type being implemented directly, implement instead. + /// + // ReSharper disable once UnusedMember.Global + internal abstract object InternalGuard { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/DataModelVisualization/DataModelVisualizationRegistration.cs b/src/Artemis.UI.Shared/DataModelVisualization/DataModelVisualizationRegistration.cs new file mode 100644 index 000000000..6aa9c65f1 --- /dev/null +++ b/src/Artemis.UI.Shared/DataModelVisualization/DataModelVisualizationRegistration.cs @@ -0,0 +1,53 @@ +using System; +using Artemis.Core; +using Artemis.Core.Plugins.Models; +using Artemis.UI.Shared.Services; + +namespace Artemis.UI.Shared.DataModelVisualization +{ + public class DataModelVisualizationRegistration + { + private readonly IDataModelVisualizationService _dataModelVisualizationService; + + public DataModelVisualizationRegistration(IDataModelVisualizationService dataModelVisualizationService, + RegistrationType registrationType, + PluginInfo pluginInfo, + Type supportedType, + Type viewModelType) + { + _dataModelVisualizationService = dataModelVisualizationService; + RegistrationType = registrationType; + PluginInfo = pluginInfo; + SupportedType = supportedType; + ViewModelType = viewModelType; + + if (PluginInfo != Constants.CorePluginInfo) + PluginInfo.Instance.PluginDisabled += InstanceOnPluginDisabled; + } + + public RegistrationType RegistrationType { get; } + public PluginInfo PluginInfo { get; } + public Type SupportedType { get; } + public Type ViewModelType { get; } + + internal void Unsubscribe() + { + if (PluginInfo != Constants.CorePluginInfo) + PluginInfo.Instance.PluginDisabled -= InstanceOnPluginDisabled; + } + + private void InstanceOnPluginDisabled(object sender, EventArgs e) + { + if (RegistrationType == RegistrationType.Input) + _dataModelVisualizationService.RemoveDataModelInput(this); + else if (RegistrationType == RegistrationType.Display) + _dataModelVisualizationService.RemoveDataModelDisplay(this); + } + } + + public enum RegistrationType + { + Display, + Input + } +} \ No newline at end of file diff --git a/src/Artemis.UI/DataModelVisualization/DataModelListViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs similarity index 78% rename from src/Artemis.UI/DataModelVisualization/DataModelListViewModel.cs rename to src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs index 885d4ce21..e61194c73 100644 --- a/src/Artemis.UI/DataModelVisualization/DataModelListViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs @@ -3,18 +3,22 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using Artemis.Core.Plugins.Abstract.DataModels.Attributes; +using Artemis.UI.Shared.Services; using Stylet; -namespace Artemis.UI.DataModelVisualization +namespace Artemis.UI.Shared.DataModelVisualization.Shared { public class DataModelListViewModel : DataModelVisualizationViewModel { + private readonly IDataModelVisualizationService _dataModelVisualizationService; private BindableCollection _children; - private IList _list; private string _count; + private IList _list; - public DataModelListViewModel(PropertyInfo propertyInfo, DataModelPropertyAttribute propertyDescription, DataModelVisualizationViewModel parent) + internal DataModelListViewModel(PropertyInfo propertyInfo, DataModelPropertyAttribute propertyDescription, DataModelVisualizationViewModel parent, + IDataModelVisualizationService dataModelVisualizationService) { + _dataModelVisualizationService = dataModelVisualizationService; PropertyInfo = propertyInfo; Parent = parent; PropertyDescription = propertyDescription; @@ -53,7 +57,7 @@ namespace Artemis.UI.DataModelVisualization DataModelVisualizationViewModel child; if (Children.Count <= index) { - child = CreateChild(item); + child = CreateChild(_dataModelVisualizationService, item); Children.Add(child); } else diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertyViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertyViewModel.cs new file mode 100644 index 000000000..cac4d9424 --- /dev/null +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertyViewModel.cs @@ -0,0 +1,59 @@ +using System.Reflection; +using Artemis.Core.Plugins.Abstract.DataModels.Attributes; + +namespace Artemis.UI.Shared.DataModelVisualization.Shared +{ + public class DataModelPropertyViewModel : DataModelVisualizationViewModel + { + private DataModelDisplayViewModel _displayViewModel; + private bool _showNull; + private bool _showToString; + private bool _showViewModel; + + internal DataModelPropertyViewModel(PropertyInfo propertyInfo, DataModelPropertyAttribute propertyDescription, DataModelVisualizationViewModel parent) + { + PropertyInfo = propertyInfo; + Parent = parent; + PropertyDescription = propertyDescription; + } + + public DataModelDisplayViewModel DisplayViewModel + { + get => _displayViewModel; + set => SetAndNotify(ref _displayViewModel, value); + } + + public bool ShowToString + { + get => _showToString; + set => SetAndNotify(ref _showToString, value); + } + + public bool ShowNull + { + get => _showNull; + set => SetAndNotify(ref _showNull, value); + } + + public bool ShowViewModel + { + get => _showViewModel; + set => SetAndNotify(ref _showViewModel, value); + } + + public override void Update() + { + if (PropertyInfo != null && Parent?.Model != null) + { + Model = PropertyInfo.GetValue(Parent.Model); + DisplayViewModel?.UpdateValue(Model); + } + + ShowToString = Model != null && DisplayViewModel == null; + ShowNull = Model == null; + ShowViewModel = Model != null && DisplayViewModel != null; + + UpdateListStatus(); + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/DataModelVisualization/DataModelViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelViewModel.cs similarity index 70% rename from src/Artemis.UI/DataModelVisualization/DataModelViewModel.cs rename to src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelViewModel.cs index 44868fa40..e93913225 100644 --- a/src/Artemis.UI/DataModelVisualization/DataModelViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelViewModel.cs @@ -1,21 +1,25 @@ using System.Reflection; using Artemis.Core.Extensions; using Artemis.Core.Plugins.Abstract.DataModels.Attributes; +using Artemis.UI.Shared.Services; using Stylet; -namespace Artemis.UI.DataModelVisualization +namespace Artemis.UI.Shared.DataModelVisualization.Shared { public class DataModelViewModel : DataModelVisualizationViewModel { + private readonly IDataModelVisualizationService _dataModelVisualizationService; private BindableCollection _children; - public DataModelViewModel() + internal DataModelViewModel() { Children = new BindableCollection(); } - public DataModelViewModel(PropertyInfo propertyInfo, object model, DataModelPropertyAttribute propertyDescription, DataModelVisualizationViewModel parent) + internal DataModelViewModel(PropertyInfo propertyInfo, object model, DataModelPropertyAttribute propertyDescription, DataModelVisualizationViewModel parent, + IDataModelVisualizationService dataModelVisualizationService) { + _dataModelVisualizationService = dataModelVisualizationService; PropertyInfo = propertyInfo; Model = model; PropertyDescription = propertyDescription; @@ -36,7 +40,7 @@ namespace Artemis.UI.DataModelVisualization Children.Clear(); foreach (var propertyInfo in Model.GetType().GetProperties()) { - var child = CreateChild(propertyInfo); + var child = CreateChild(_dataModelVisualizationService, propertyInfo); if (child != null) Children.Add(child); } diff --git a/src/Artemis.UI/DataModelVisualization/DataModelVisualizationViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs similarity index 75% rename from src/Artemis.UI/DataModelVisualization/DataModelVisualizationViewModel.cs rename to src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs index 791b1ca79..1bb7f96b8 100644 --- a/src/Artemis.UI/DataModelVisualization/DataModelVisualizationViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs @@ -3,20 +3,25 @@ using System.Collections; using System.Reflection; using Artemis.Core.Extensions; using Artemis.Core.Plugins.Abstract.DataModels.Attributes; +using Artemis.UI.Shared.Services; using Humanizer; using Stylet; -namespace Artemis.UI.DataModelVisualization +namespace Artemis.UI.Shared.DataModelVisualization.Shared { public abstract class DataModelVisualizationViewModel : PropertyChangedBase { + private bool _isListProperty; + private string _listDescription; + private object _model; + private DataModelVisualizationViewModel _parent; private DataModelPropertyAttribute _propertyDescription; private PropertyInfo _propertyInfo; private Type _propertyType; - private DataModelVisualizationViewModel _parent; - private object _model; - private bool _isListProperty; - private string _listDescription; + + internal DataModelVisualizationViewModel() + { + } public DataModelPropertyAttribute PropertyDescription { @@ -62,7 +67,7 @@ namespace Artemis.UI.DataModelVisualization public abstract void Update(); - protected DataModelVisualizationViewModel CreateChild(PropertyInfo propertyInfo) + protected DataModelVisualizationViewModel CreateChild(IDataModelVisualizationService dataModelVisualizationService, PropertyInfo propertyInfo) { // Skip properties decorated with DataModelIgnore if (Attribute.IsDefined(propertyInfo, typeof(DataModelIgnoreAttribute))) @@ -73,11 +78,15 @@ namespace Artemis.UI.DataModelVisualization if (dataModelPropertyAttribute == null) dataModelPropertyAttribute = new DataModelPropertyAttribute {Name = propertyInfo.Name.Humanize()}; + // If a display VM was found, prefer to use that in any case + var typeViewModel = dataModelVisualizationService.GetDataModelDisplayViewModel(propertyInfo.PropertyType); + if (typeViewModel != null) + return new DataModelPropertyViewModel(propertyInfo, dataModelPropertyAttribute, this) {DisplayViewModel = typeViewModel}; // For primitives, create a property view model, it may be null that is fine if (propertyInfo.PropertyType.IsPrimitive || propertyInfo.PropertyType == typeof(string)) return new DataModelPropertyViewModel(propertyInfo, dataModelPropertyAttribute, this); if (typeof(IList).IsAssignableFrom(propertyInfo.PropertyType)) - return new DataModelListViewModel(propertyInfo, dataModelPropertyAttribute, this); + return new DataModelListViewModel(propertyInfo, dataModelPropertyAttribute, this, dataModelVisualizationService); // For other value types create a child view model if the value type is not null if (propertyInfo.PropertyType.IsClass || propertyInfo.PropertyType.IsStruct()) { @@ -85,22 +94,26 @@ namespace Artemis.UI.DataModelVisualization if (value == null) return null; - return new DataModelViewModel(propertyInfo, value, dataModelPropertyAttribute, this); + return new DataModelViewModel(propertyInfo, value, dataModelPropertyAttribute, this, dataModelVisualizationService); } return null; } - protected DataModelVisualizationViewModel CreateChild(object value) + protected DataModelVisualizationViewModel CreateChild(IDataModelVisualizationService dataModelVisualizationService, object value) { var dataModelPropertyAttribute = new DataModelPropertyAttribute {Name = "Unknown property"}; + // If a display VM was found, prefer to use that in any case + var typeViewModel = dataModelVisualizationService.GetDataModelDisplayViewModel(value.GetType()); + if (typeViewModel != null) + return new DataModelPropertyViewModel(null, dataModelPropertyAttribute, this) {Model = value, DisplayViewModel = typeViewModel}; // For primitives, create a property view model, it may be null that is fine if (value.GetType().IsPrimitive || value is string) return new DataModelPropertyViewModel(null, dataModelPropertyAttribute, this) {Model = value}; // For other value types create a child view model if the value type is not null if (value.GetType().IsClass || value.GetType().IsStruct()) - return new DataModelViewModel(null, value, dataModelPropertyAttribute, this); + return new DataModelViewModel(null, value, dataModelPropertyAttribute, this, dataModelVisualizationService); return null; } diff --git a/src/Artemis.UI.Shared/PropertyInput/PropertyInputRegistration.cs b/src/Artemis.UI.Shared/PropertyInput/PropertyInputRegistration.cs index 2d00f3074..425c4e827 100644 --- a/src/Artemis.UI.Shared/PropertyInput/PropertyInputRegistration.cs +++ b/src/Artemis.UI.Shared/PropertyInput/PropertyInputRegistration.cs @@ -30,15 +30,10 @@ namespace Artemis.UI.Shared.PropertyInput PluginInfo.Instance.PluginDisabled -= InstanceOnPluginDisabled; } - internal void Remove() - { - // It'll call Unsubscribe for us - _profileEditorService.RemovePropertyInput(this); - } - private void InstanceOnPluginDisabled(object sender, EventArgs e) { - Remove(); + // Profile editor service will call Unsubscribe + _profileEditorService.RemovePropertyInput(this); } } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/PropertyInput/PropertyInputViewModel.cs b/src/Artemis.UI.Shared/PropertyInput/PropertyInputViewModel.cs index ab1856305..b2f59f322 100644 --- a/src/Artemis.UI.Shared/PropertyInput/PropertyInputViewModel.cs +++ b/src/Artemis.UI.Shared/PropertyInput/PropertyInputViewModel.cs @@ -5,10 +5,10 @@ using Stylet; namespace Artemis.UI.Shared.PropertyInput { - public abstract class PropertyInputViewModel : ValidatingModelBase, IDisposable + public abstract class PropertyInputViewModel : PropertyInputViewModel { - private T _inputValue; private bool _inputDragging; + private T _inputValue; protected PropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService) { @@ -30,6 +30,7 @@ namespace Artemis.UI.Shared.PropertyInput public LayerProperty LayerProperty { get; } public IProfileEditorService ProfileEditorService { get; } + internal override object InternalGuard { get; } = null; public bool InputDragging { @@ -47,12 +48,15 @@ namespace Artemis.UI.Shared.PropertyInput } } - public virtual void Dispose() + public override void Dispose() { LayerProperty.Updated -= LayerPropertyOnUpdated; LayerProperty.BaseValueChanged -= LayerPropertyOnUpdated; + Dispose(true); + GC.SuppressFinalize(this); } + protected virtual void OnInputValueApplied() { } @@ -97,7 +101,6 @@ namespace Artemis.UI.Shared.PropertyInput Validate(); } - #region Event handlers public void InputDragStarted(object sender, EventArgs e) @@ -122,4 +125,33 @@ namespace Artemis.UI.Shared.PropertyInput #endregion } + + /// + /// For internal use only, implement instead. + /// + public abstract class PropertyInputViewModel : ValidatingModelBase, IDisposable + { + protected PropertyInputViewModel() + { + } + + protected PropertyInputViewModel(IModelValidator validator) : base(validator) + { + } + + /// + /// Prevents this type being implemented directly, implement instead. + /// + // ReSharper disable once UnusedMember.Global + internal abstract object InternalGuard { get; } + + public abstract void Dispose(); + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + } + } + } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/DataModelVisualizationService.cs b/src/Artemis.UI.Shared/Services/DataModelVisualizationService.cs new file mode 100644 index 000000000..4810900e5 --- /dev/null +++ b/src/Artemis.UI.Shared/Services/DataModelVisualizationService.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Artemis.Core.Plugins.Abstract; +using Artemis.Core.Plugins.Exceptions; +using Artemis.Core.Plugins.Models; +using Artemis.Core.Services.Interfaces; +using Artemis.UI.Shared.DataModelVisualization; +using Artemis.UI.Shared.DataModelVisualization.Shared; +using Artemis.UI.Shared.Services.Interfaces; +using Ninject; + +namespace Artemis.UI.Shared.Services +{ + public class DataModelVisualizationService : IDataModelVisualizationService + { + private readonly IDataModelService _dataModelService; + private readonly IKernel _kernel; + private readonly List _registeredDataModelDisplays; + private readonly List _registeredDataModelEditors; + + public DataModelVisualizationService(IDataModelService dataModelService, IKernel kernel) + { + _dataModelService = dataModelService; + _kernel = kernel; + _registeredDataModelEditors = new List(); + _registeredDataModelDisplays = new List(); + } + + public DataModelViewModel GetMainDataModelVisualization() + { + var viewModel = new DataModelViewModel(); + foreach (var dataModelExpansion in _dataModelService.DataModelExpansions) + viewModel.Children.Add(new DataModelViewModel(null, dataModelExpansion, dataModelExpansion.DataModelDescription, viewModel, this)); + + return viewModel; + } + + public DataModelViewModel GetPluginDataModelVisualization(Plugin plugin) + { + var dataModel = _dataModelService.GetPluginDataModel(plugin); + if (dataModel == null) + return null; + + var viewModel = new DataModelViewModel(); + viewModel.Children.Add(new DataModelViewModel(null, dataModel, dataModel.DataModelDescription, viewModel, this)); + return viewModel; + } + + public DataModelVisualizationRegistration RegisterDataModelInput(PluginInfo pluginInfo) where T : DataModelInputViewModel + { + var viewModelType = typeof(T); + lock (_registeredDataModelEditors) + { + var supportedType = viewModelType.BaseType.GetGenericArguments()[0]; + var existing = _registeredDataModelEditors.FirstOrDefault(r => r.SupportedType == supportedType); + if (existing != null) + { + if (existing.PluginInfo != pluginInfo) + throw new ArtemisPluginException($"Cannot register property editor for type {supportedType.Name} because an editor was already registered by {pluginInfo.Name}"); + return existing; + } + + _kernel.Bind(viewModelType).ToSelf(); + var registration = new DataModelVisualizationRegistration(this, RegistrationType.Input, pluginInfo, supportedType, viewModelType); + _registeredDataModelEditors.Add(registration); + return registration; + } + } + + public DataModelVisualizationRegistration RegisterDataModelDisplay(PluginInfo pluginInfo) where T : DataModelDisplayViewModel + { + var viewModelType = typeof(T); + lock (_registeredDataModelDisplays) + { + var supportedType = viewModelType.BaseType.GetGenericArguments()[0]; + var existing = _registeredDataModelDisplays.FirstOrDefault(r => r.SupportedType == supportedType); + if (existing != null) + { + if (existing.PluginInfo != pluginInfo) + throw new ArtemisPluginException($"Cannot register property editor for type {supportedType.Name} because an editor was already registered by {pluginInfo.Name}"); + return existing; + } + + _kernel.Bind(viewModelType).ToSelf(); + var registration = new DataModelVisualizationRegistration(this, RegistrationType.Display, pluginInfo, supportedType, viewModelType); + _registeredDataModelDisplays.Add(registration); + return registration; + } + } + + public void RemoveDataModelInput(DataModelVisualizationRegistration registration) + { + lock (_registeredDataModelEditors) + { + if (_registeredDataModelEditors.Contains(registration)) + { + registration.Unsubscribe(); + _registeredDataModelEditors.Remove(registration); + + _kernel.Unbind(registration.ViewModelType); + } + } + } + + public void RemoveDataModelDisplay(DataModelVisualizationRegistration registration) + { + lock (_registeredDataModelDisplays) + { + if (_registeredDataModelDisplays.Contains(registration)) + { + registration.Unsubscribe(); + _registeredDataModelDisplays.Remove(registration); + + _kernel.Unbind(registration.ViewModelType); + } + } + } + + public DataModelDisplayViewModel GetDataModelDisplayViewModel(Type propertyType) + { + lock (_registeredDataModelDisplays) + { + var match = _registeredDataModelDisplays.FirstOrDefault(d => d.SupportedType == propertyType); + if (match != null) + return (DataModelDisplayViewModel) _kernel.Get(match.ViewModelType); + return null; + } + } + } + + public interface IDataModelVisualizationService : IArtemisSharedUIService + { + DataModelViewModel GetMainDataModelVisualization(); + DataModelViewModel GetPluginDataModelVisualization(Plugin plugin); + + DataModelVisualizationRegistration RegisterDataModelInput(PluginInfo pluginInfo) where T : DataModelInputViewModel; + DataModelVisualizationRegistration RegisterDataModelDisplay(PluginInfo pluginInfo) where T : DataModelDisplayViewModel; + void RemoveDataModelInput(DataModelVisualizationRegistration registration); + void RemoveDataModelDisplay(DataModelVisualizationRegistration registration); + + DataModelDisplayViewModel GetDataModelDisplayViewModel(Type propertyType); + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs b/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs index 2623b8c32..bc40f25fe 100644 --- a/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs @@ -63,7 +63,15 @@ namespace Artemis.UI.Shared.Services.Interfaces /// event EventHandler ProfilePreviewUpdated; - PropertyInputRegistration RegisterPropertyInput(PluginInfo pluginInfo, Type viewModelType); + /// + /// 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(PluginInfo pluginInfo) where T : PropertyInputViewModel; + void RemovePropertyInput(PropertyInputRegistration registration); } } \ 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 ccfcdbe91..f48cd18ef 100644 --- a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs @@ -103,6 +103,7 @@ namespace Artemis.UI.Shared.Services foreach (var baseLayerEffect in folder.LayerEffects) baseLayerEffect.Update(delta.TotalSeconds); } + foreach (var layer in SelectedProfile.GetAllLayers()) { layer.OverrideProgress(CurrentTime); @@ -153,12 +154,9 @@ namespace Artemis.UI.Shared.Services UpdateProfilePreview(); } - public PropertyInputRegistration RegisterPropertyInput(PluginInfo pluginInfo, Type viewModelType) + public PropertyInputRegistration RegisterPropertyInput(PluginInfo pluginInfo) where T : PropertyInputViewModel { - // Bit ugly to do a name comparison but I don't know a nicer way right now - if (viewModelType.BaseType == null || viewModelType.BaseType.Name != typeof(PropertyInputViewModel<>).Name) - throw new ArtemisPluginException($"{nameof(viewModelType)} base type must be of type PropertyInputViewModel"); - + var viewModelType = typeof(T); lock (_registeredPropertyEditors) { var supportedType = viewModelType.BaseType.GetGenericArguments()[0]; diff --git a/src/Artemis.UI/DataModelVisualization/DataModelPropertyViewModel.cs b/src/Artemis.UI/DataModelVisualization/DataModelPropertyViewModel.cs deleted file mode 100644 index 49ab9f955..000000000 --- a/src/Artemis.UI/DataModelVisualization/DataModelPropertyViewModel.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Reflection; -using Artemis.Core.Plugins.Abstract.DataModels.Attributes; - -namespace Artemis.UI.DataModelVisualization -{ - public class DataModelPropertyViewModel : DataModelVisualizationViewModel - { - public DataModelPropertyViewModel(PropertyInfo propertyInfo, DataModelPropertyAttribute propertyDescription, DataModelVisualizationViewModel parent) - { - PropertyInfo = propertyInfo; - Parent = parent; - PropertyDescription = propertyDescription; - } - - public override void Update() - { - if (PropertyInfo != null && Parent?.Model != null) - Model = PropertyInfo.GetValue(Parent.Model); - - UpdateListStatus(); - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/DataModelVisualization/SKColorDataModelDisplayView.xaml b/src/Artemis.UI/DataModelVisualization/SKColorDataModelDisplayView.xaml new file mode 100644 index 000000000..2e55a49de --- /dev/null +++ b/src/Artemis.UI/DataModelVisualization/SKColorDataModelDisplayView.xaml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/DataModelVisualization/SKColorDataModelDisplayViewModel.cs b/src/Artemis.UI/DataModelVisualization/SKColorDataModelDisplayViewModel.cs new file mode 100644 index 000000000..cd1eff507 --- /dev/null +++ b/src/Artemis.UI/DataModelVisualization/SKColorDataModelDisplayViewModel.cs @@ -0,0 +1,9 @@ +using Artemis.UI.Shared.DataModelVisualization; +using SkiaSharp; + +namespace Artemis.UI.DataModelVisualization +{ + public class SKColorDataModelDisplayViewModel : DataModelDisplayViewModel + { + } +} \ No newline at end of file diff --git a/src/Artemis.UI/PropertyInput/SKPointPropertyInputViewModel.cs b/src/Artemis.UI/PropertyInput/SKPointPropertyInputViewModel.cs index 911445cf9..437b790ec 100644 --- a/src/Artemis.UI/PropertyInput/SKPointPropertyInputViewModel.cs +++ b/src/Artemis.UI/PropertyInput/SKPointPropertyInputViewModel.cs @@ -4,7 +4,6 @@ using Artemis.Core.Models.Profile.LayerProperties; using Artemis.UI.Shared.PropertyInput; using Artemis.UI.Shared.Services.Interfaces; using FluentValidation; -// using PropertyChanged; using SkiaSharp; using Stylet; @@ -17,15 +16,12 @@ namespace Artemis.UI.PropertyInput { } - // Since SKPoint is immutable we need to create properties that replace the SKPoint entirely - // [DependsOn(nameof(InputValue))] public float X { get => InputValue.X; set => InputValue = new SKPoint(value, Y); } - // [DependsOn(nameof(InputValue))] public float Y { get => InputValue.Y; diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml index ef97ae43f..f26191f39 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml @@ -4,11 +4,11 @@ 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.Settings.Debug.Tabs" - xmlns:dataModel="clr-namespace:Artemis.UI.DataModelVisualization" xmlns:s="https://github.com/canton7/Stylet" xmlns:converters="clr-namespace:Artemis.UI.Converters" xmlns:wpf="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:plugins="clr-namespace:Artemis.Core.Plugins.Abstract;assembly=Artemis.Core" + xmlns:dataModel="clr-namespace:Artemis.UI.Shared.DataModelVisualization.Shared;assembly=Artemis.UI.Shared" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance local:DataModelDebugViewModel}"> @@ -101,6 +101,8 @@ + + [] @@ -112,17 +114,24 @@ Text="{Binding ListDescription}" ToolTip="{Binding PropertyDescription.Description}" Visibility="{Binding IsListProperty, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" /> + + + Visibility="{Binding ShowToString, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" /> + Visibility="{Binding ShowNull, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" /> + + diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs index f4f867571..0a3918226 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs @@ -3,8 +3,9 @@ using System.Linq; using System.Timers; using Artemis.Core.Events; using Artemis.Core.Services.Interfaces; -using Artemis.UI.DataModelVisualization; using Artemis.UI.Services; +using Artemis.UI.Shared.DataModelVisualization.Shared; +using Artemis.UI.Shared.Services; using Stylet; namespace Artemis.UI.Screens.Settings.Debug.Tabs diff --git a/src/Artemis.UI/Services/DataModelVisualizationService.cs b/src/Artemis.UI/Services/DataModelVisualizationService.cs deleted file mode 100644 index 8e7e2a54d..000000000 --- a/src/Artemis.UI/Services/DataModelVisualizationService.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Artemis.Core.Plugins.Abstract; -using Artemis.Core.Services.Interfaces; -using Artemis.UI.DataModelVisualization; -using Artemis.UI.Services.Interfaces; - -namespace Artemis.UI.Services -{ - public class DataModelVisualizationService : IDataModelVisualizationService - { - private readonly IDataModelService _dataModelService; - - public DataModelVisualizationService(IDataModelService dataModelService) - { - _dataModelService = dataModelService; - } - - public DataModelViewModel GetMainDataModelVisualization() - { - var viewModel = new DataModelViewModel(); - foreach (var dataModelExpansion in _dataModelService.DataModelExpansions) - viewModel.Children.Add(new DataModelViewModel(null, dataModelExpansion, dataModelExpansion.DataModelDescription, viewModel)); - - return viewModel; - } - - public DataModelViewModel GetPluginDataModelVisualization(Plugin plugin) - { - var dataModel = _dataModelService.GetPluginDataModel(plugin); - if (dataModel == null) - return null; - - var viewModel = new DataModelViewModel(); - viewModel.Children.Add(new DataModelViewModel(null, dataModel, dataModel.DataModelDescription, viewModel)); - return viewModel; - } - } - - public interface IDataModelVisualizationService : IArtemisUIService - { - DataModelViewModel GetMainDataModelVisualization(); - DataModelViewModel GetPluginDataModelVisualization(Plugin plugin); - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Services/DebugService.cs b/src/Artemis.UI/Services/DebugService.cs index 21e143692..54f02c062 100644 --- a/src/Artemis.UI/Services/DebugService.cs +++ b/src/Artemis.UI/Services/DebugService.cs @@ -1,6 +1,10 @@ -using System.Windows; +using System; +using System.Windows; +using Artemis.Core; +using Artemis.UI.DataModelVisualization; using Artemis.UI.Screens.Settings.Debug; using Artemis.UI.Services.Interfaces; +using Artemis.UI.Shared.Services; using MaterialDesignExtensions.Controls; using Ninject; using Stylet; @@ -11,12 +15,21 @@ namespace Artemis.UI.Services { private readonly IKernel _kernel; private readonly IWindowManager _windowManager; + private readonly IDataModelVisualizationService _dataModelVisualizationService; private DebugViewModel _debugViewModel; - public DebugService(IKernel kernel, IWindowManager windowManager) + public DebugService(IKernel kernel, IWindowManager windowManager, IDataModelVisualizationService dataModelVisualizationService) { _kernel = kernel; _windowManager = windowManager; + _dataModelVisualizationService = dataModelVisualizationService; + + RegisterBuiltInDataModelDisplays(); + } + + private void RegisterBuiltInDataModelDisplays() + { + _dataModelVisualizationService.RegisterDataModelDisplay(Constants.CorePluginInfo); } public void ShowDebugger() diff --git a/src/Artemis.UI/Services/LayerEditorService.cs b/src/Artemis.UI/Services/LayerEditorService.cs index c9dc4c7d3..f17726205 100644 --- a/src/Artemis.UI/Services/LayerEditorService.cs +++ b/src/Artemis.UI/Services/LayerEditorService.cs @@ -141,13 +141,13 @@ namespace Artemis.UI.Services private void RegisterBuiltInPropertyEditors() { - _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo, typeof(BrushPropertyInputViewModel)); - _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo, typeof(ColorGradientPropertyInputViewModel)); - _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo, typeof(FloatPropertyInputViewModel)); - _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo, typeof(IntPropertyInputViewModel)); - _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo, typeof(SKColorPropertyInputViewModel)); - _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo, typeof(SKPointPropertyInputViewModel)); - _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo, typeof(SKSizePropertyInputViewModel)); + _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo); + _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo); + _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo); + _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo); + _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo); + _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo); + _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo); } } } \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrushProvider.cs b/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrushProvider.cs index 814843b31..a7a18eb7f 100644 --- a/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrushProvider.cs +++ b/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrushProvider.cs @@ -6,7 +6,7 @@ namespace Artemis.Plugins.LayerBrushes.Color { public override void EnablePlugin() { - AddLayerBrushDescriptor("Color", "A brush supporting solid colors and multiple types of gradients", "Brush"); + RegisterLayerBrushDescriptor("Color", "A brush supporting solid colors and multiple types of gradients", "Brush"); } public override void DisablePlugin() diff --git a/src/Plugins/Artemis.Plugins.LayerBrushes.ColorRgbNet/RgbNetColorBrushProvider.cs b/src/Plugins/Artemis.Plugins.LayerBrushes.ColorRgbNet/RgbNetColorBrushProvider.cs index fe71aae4d..2c511a8e5 100644 --- a/src/Plugins/Artemis.Plugins.LayerBrushes.ColorRgbNet/RgbNetColorBrushProvider.cs +++ b/src/Plugins/Artemis.Plugins.LayerBrushes.ColorRgbNet/RgbNetColorBrushProvider.cs @@ -15,8 +15,8 @@ namespace Artemis.Plugins.LayerBrushes.ColorRgbNet public override void EnablePlugin() { - _profileEditorService.RegisterPropertyInput(PluginInfo, typeof(StringPropertyInputViewModel)); - AddLayerBrushDescriptor("RGB.NET Color", "A RGB.NET based color", "Brush"); + _profileEditorService.RegisterPropertyInput(PluginInfo); + RegisterLayerBrushDescriptor("RGB.NET Color", "A RGB.NET based color", "Brush"); } public override void DisablePlugin() diff --git a/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrushProvider.cs b/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrushProvider.cs index ba643df02..9bc4017f9 100644 --- a/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrushProvider.cs +++ b/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrushProvider.cs @@ -6,7 +6,7 @@ namespace Artemis.Plugins.LayerBrushes.Noise { public override void EnablePlugin() { - AddLayerBrushDescriptor("Noise", "A brush of that shows an animated random noise", "ScatterPlot"); + RegisterLayerBrushDescriptor("Noise", "A brush of that shows an animated random noise", "ScatterPlot"); } public override void DisablePlugin() diff --git a/src/Plugins/Artemis.Plugins.Modules.General/GeneralDataModel.cs b/src/Plugins/Artemis.Plugins.Modules.General/GeneralDataModel.cs index 4e53c6a3b..a5a397bf3 100644 --- a/src/Plugins/Artemis.Plugins.Modules.General/GeneralDataModel.cs +++ b/src/Plugins/Artemis.Plugins.Modules.General/GeneralDataModel.cs @@ -20,6 +20,8 @@ namespace Artemis.Plugins.Modules.General [DataModelProperty(Name = "A test boolean", Description = "This is a test boolean that's not of any use outside testing!")] public bool TestBoolean { get; set; } + public SKColor TestColor { get; set; } = new SKColor(221, 21, 152); + [DataModelProperty(Name = "Player info", Description = "[TEST] Contains information about the player")] public PlayerInfo PlayerInfo { get; set; }