From 8d901027ee89949ed60b991da9f66304889fdd0e Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Fri, 20 Nov 2020 23:13:37 +0100 Subject: [PATCH] Shared UI - Resolved all remaining warnings UI - Resolved all remaining warnings Layer properties - Fixed DisableKeyframes layer property attribute not being applied --- .../Profile/LayerProperties/LayerProperty.cs | 3 + .../Controls/DeviceVisualizer.cs | 6 +- .../Converters/ColorToStringConverter.cs | 8 +- .../Converters/SKColorToStringConverter.cs | 8 +- .../Converters/TypeToStringConverter.cs | 2 +- .../DataModelDisplayViewModel.cs | 17 +- .../DataModelInputViewModel.cs | 10 +- .../Input/DataModelDynamicViewModel.cs | 152 +++++++++++++++--- .../Input/DataModelStaticViewModel.cs | 34 ++-- .../Shared/DataModelEventViewModel.cs | 20 ++- .../DataModelListPropertiesViewModel.cs | 43 +++-- .../Shared/DataModelListPropertyViewModel.cs | 17 +- .../Shared/DataModelListViewModel.cs | 21 +-- .../Shared/DataModelPropertiesViewModel.cs | 18 +-- .../Shared/DataModelPropertyViewModel.cs | 27 ++-- .../Shared/DataModelVisualizationViewModel.cs | 82 +++++----- .../Events/DataModelInputDynamicEventArgs.cs | 4 +- .../Events/DataModelInputStaticEventArgs.cs | 4 +- .../Events/ProfileEventArgs.cs | 6 +- .../Events/RenderProfileElementEventArgs.cs | 6 +- .../LayerBrushConfigurationDialog.cs | 5 - .../LayerEffectConfigurationDialog.cs | 3 +- .../Plugins/PluginConfigurationDialog.cs | 5 - .../Properties/Annotations.cs | 5 +- .../PropertyInput/PropertyInputViewModel.cs | 4 +- .../Screens/Dialogs/ConfirmDialogViewModel.cs | 4 +- .../Screens/Exceptions/ExceptionViewModel.cs | 6 +- .../GradientEditor/ColorStopViewModel.cs | 8 +- .../GradientEditor/GradientEditorViewModel.cs | 16 +- .../Services/ColorPickerService.cs | 2 +- .../Services/DataModelUIService.cs | 50 +++--- .../Services/Dialog/DialogService.cs | 53 +++--- .../Services/Dialog/DialogViewModelHost.cs | 2 +- .../Interfaces/IDataModelUIService.cs | 6 +- .../Interfaces/IProfileEditorService.cs | 16 +- .../Services/ProfileEditorService.cs | 87 ++++++---- .../Utilities/ShortcutUtilities.cs | 21 ++- .../Utilities/VisualTreeUtilities.cs | 8 +- src/Artemis.UI/Bootstrapper.cs | 15 +- .../Tabs/ActivationRequirementViewModel.cs | 15 +- .../DataModelConditionPredicateViewModel.cs | 36 +++-- .../DataModelConditionEventViewModel.cs | 6 +- .../DataModelConditionListViewModel.cs | 10 +- ...taModelConditionEventPredicateViewModel.cs | 1 + ...ModelConditionGeneralPredicateViewModel.cs | 1 + ...ataModelConditionListPredicateViewModel.cs | 1 + .../DisplayConditionsViewModel.cs | 2 +- .../ConditionalDataBindingModeViewModel.cs | 5 +- .../DataBindingConditionViewModel.cs | 19 ++- .../DataBindings/DataBindingViewModel.cs | 53 +++--- .../DataBindings/DataBindingsViewModel.cs | 2 +- .../DataBindingModifierViewModel.cs | 7 +- .../DirectDataBindingModeViewModel.cs | 3 +- .../LayerPropertyGroupViewModel.cs | 15 +- .../LayerProperties/LayerPropertyViewModel.cs | 2 +- .../Timeline/TimelineGroupViewModel.cs | 2 +- .../Timeline/TimelineKeyframeViewModel.cs | 5 +- .../Timeline/TimelinePropertyViewModel.cs | 2 +- .../Timeline/TimelineSegmentViewModel.cs | 91 +++++------ .../Timeline/TimelineViewModel.cs | 38 ++--- .../Tree/TreePropertyViewModel.cs | 82 +++++----- .../ProfileTree/TreeItem/LayerViewModel.cs | 3 +- .../ProfileTree/TreeItem/TreeItemViewModel.cs | 27 ++-- .../Visualization/ProfileViewModel.cs | 2 +- .../Visualization/Tools/EditToolViewModel.cs | 17 +- src/Artemis.UI/Screens/RootViewModel.cs | 29 ++-- .../Debug/Tabs/DataModelDebugViewModel.cs | 27 +++- .../Devices/DeviceSettingsTabViewModel.cs | 1 - .../Tabs/Plugins/PluginFeatureViewModel.cs | 29 ++-- .../Tabs/Plugins/PluginSettingsViewModel.cs | 4 +- .../Screens/Sidebar/SidebarViewModel.cs | 11 +- .../Screens/Splash/SplashViewModel.cs | 4 +- src/Artemis.UI/Services/LayerEditorService.cs | 11 +- .../Services/RegistrationService.cs | 2 +- .../Stylet/FluentValidationAdapter.cs | 3 +- 75 files changed, 810 insertions(+), 562 deletions(-) diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs index 67d653575..fd9cab497 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs @@ -491,6 +491,9 @@ namespace Artemis.Core Entity = entity ?? throw new ArgumentNullException(nameof(entity)); PropertyDescription = description ?? throw new ArgumentNullException(nameof(description)); IsLoadedFromStorage = fromStorage; + + if (PropertyDescription.DisableKeyframes) + KeyframesSupported = false; } /// diff --git a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs index cf8a17afe..1a60ac398 100644 --- a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs +++ b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs @@ -184,7 +184,7 @@ namespace Artemis.UI.Shared return rotationRect.Size; } - private void OnUnloaded(object sender, RoutedEventArgs e) + private void OnUnloaded(object? sender, RoutedEventArgs e) { _timer.Stop(); @@ -196,12 +196,12 @@ namespace Artemis.UI.Shared } } - private void OnLoaded(object sender, RoutedEventArgs e) + private void OnLoaded(object? sender, RoutedEventArgs e) { _timer.Start(); } - private void TimerOnTick(object sender, EventArgs e) + private void TimerOnTick(object? sender, EventArgs e) { if (ShowColors && Visibility == Visibility.Visible) Render(); diff --git a/src/Artemis.UI.Shared/Converters/ColorToStringConverter.cs b/src/Artemis.UI.Shared/Converters/ColorToStringConverter.cs index 6b008793a..4519d8b83 100644 --- a/src/Artemis.UI.Shared/Converters/ColorToStringConverter.cs +++ b/src/Artemis.UI.Shared/Converters/ColorToStringConverter.cs @@ -13,20 +13,20 @@ namespace Artemis.UI.Shared public class ColorToStringConverter : IValueConverter { /// - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object? Convert(object? value, Type targetType, object parameter, CultureInfo culture) { return value?.ToString()?.ToUpper(); } /// - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object? ConvertBack(object? value, Type targetType, object parameter, CultureInfo culture) { try { - if (string.IsNullOrWhiteSpace((string) value)) + if (string.IsNullOrWhiteSpace(value as string)) return default(Color); - object color = ColorConverter.ConvertFromString((string) value); + object? color = ColorConverter.ConvertFromString((string) value!); if (color is Color c) return c; diff --git a/src/Artemis.UI.Shared/Converters/SKColorToStringConverter.cs b/src/Artemis.UI.Shared/Converters/SKColorToStringConverter.cs index ca3e04dca..d1dbfcde8 100644 --- a/src/Artemis.UI.Shared/Converters/SKColorToStringConverter.cs +++ b/src/Artemis.UI.Shared/Converters/SKColorToStringConverter.cs @@ -14,18 +14,18 @@ namespace Artemis.UI.Shared public class SKColorToStringConverter : IValueConverter { /// - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + 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) + public object ConvertBack(object? value, Type targetType, object parameter, CultureInfo culture) { - if (string.IsNullOrWhiteSpace((string) value)) + if (string.IsNullOrWhiteSpace(value as string)) return SKColor.Empty; - return SKColor.TryParse((string) value, out SKColor color) ? color : SKColor.Empty; + return SKColor.TryParse((string) value!, out SKColor color) ? color : SKColor.Empty; } } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Converters/TypeToStringConverter.cs b/src/Artemis.UI.Shared/Converters/TypeToStringConverter.cs index 3afd7cc80..10570fa8a 100644 --- a/src/Artemis.UI.Shared/Converters/TypeToStringConverter.cs +++ b/src/Artemis.UI.Shared/Converters/TypeToStringConverter.cs @@ -11,7 +11,7 @@ namespace Artemis.UI.Shared public class TypeToStringConverter : IValueConverter { /// - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object? Convert(object value, Type targetType, object? parameter, CultureInfo culture) { bool humanizeProvided = bool.TryParse(parameter?.ToString(), out bool humanize); if (value is Type type) diff --git a/src/Artemis.UI.Shared/DataModelVisualization/DataModelDisplayViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/DataModelDisplayViewModel.cs index 623ed8a17..c9f198ebe 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/DataModelDisplayViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/DataModelDisplayViewModel.cs @@ -1,4 +1,5 @@ -using Artemis.Core.DataModelExpansions; +using System.Diagnostics.CodeAnalysis; +using Artemis.Core.DataModelExpansions; using Stylet; namespace Artemis.UI.Shared @@ -9,11 +10,13 @@ namespace Artemis.UI.Shared /// The type of the data model public abstract class DataModelDisplayViewModel : DataModelDisplayViewModel { - private T _displayValue; + [AllowNull] + private T _displayValue = default!; /// /// Gets or sets value that the view model must display /// + [AllowNull] public T DisplayValue { get => _displayValue; @@ -24,10 +27,10 @@ namespace Artemis.UI.Shared } } - internal override object InternalGuard => null; + internal override object InternalGuard => new object(); /// - public override void UpdateValue(object model) + public override void UpdateValue(object? model) { DisplayValue = model is T value ? value : default; } @@ -45,12 +48,12 @@ namespace Artemis.UI.Shared /// public abstract class DataModelDisplayViewModel : PropertyChangedBase { - private DataModelPropertyAttribute _propertyDescription; + private DataModelPropertyAttribute? _propertyDescription; /// /// Gets the property description of this value /// - public DataModelPropertyAttribute PropertyDescription + public DataModelPropertyAttribute? PropertyDescription { get => _propertyDescription; internal set => SetAndNotify(ref _propertyDescription, value); @@ -65,6 +68,6 @@ namespace Artemis.UI.Shared /// Updates the display value /// /// The value to set - public abstract void UpdateValue(object model); + public 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 index 0bb4e1a46..171e2a4c6 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/DataModelInputViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/DataModelInputViewModel.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using System.Windows; using System.Windows.Data; @@ -16,7 +17,7 @@ namespace Artemis.UI.Shared public abstract class DataModelInputViewModel : DataModelInputViewModel { private bool _closed; - private T _inputValue; + [AllowNull] private T _inputValue = default!; /// /// Creates a new instance of the class @@ -32,6 +33,7 @@ namespace Artemis.UI.Shared /// /// Gets or sets the value shown in the input /// + [AllowNull] public T InputValue { get => _inputValue; @@ -82,13 +84,13 @@ namespace Artemis.UI.Shared // ReSharper disable once UnusedMember.Global internal abstract object InternalGuard { get; } - internal Action UpdateCallback { get; set; } + internal Action UpdateCallback { get; set; } = null!; // Set right after construction /// /// Gets the types this input view model can support through type conversion. This list is defined when registering the /// view model. /// - internal IReadOnlyCollection CompatibleConversionTypes { get; set; } + internal IReadOnlyCollection? CompatibleConversionTypes { get; set; } /// /// Submits the input value and removes this view model. @@ -133,6 +135,6 @@ namespace Artemis.UI.Shared } /// - public UIElement View { get; set; } + public UIElement? View { get; set; } } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicViewModel.cs index efe20a809..dbd206a22 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicViewModel.cs @@ -12,21 +12,25 @@ using Stylet; namespace Artemis.UI.Shared.Input { + /// + /// Represents a view model that allows selecting a data model property used by boolean operations on a certain data + /// model property + /// public class DataModelDynamicViewModel : PropertyChangedBase, IDisposable { private readonly IDataModelUIService _dataModelUIService; private readonly Module _module; private readonly Timer _updateTimer; private SolidColorBrush _buttonBrush = new SolidColorBrush(Color.FromRgb(171, 71, 188)); - private DataModelPath _dataModelPath; - private DataModelPropertiesViewModel _dataModelViewModel; + private DataModelPath? _dataModelPath; + private DataModelPropertiesViewModel? _dataModelViewModel; private bool _displaySwitchButton; - private Type[] _filterTypes; + private Type[] _filterTypes = new Type[0]; private bool _isDataModelViewModelOpen; private bool _isEnabled = true; private string _placeholder = "Select a property"; - public DataModelDynamicViewModel(Module module, ISettingsService settingsService, IDataModelUIService dataModelUIService) + internal DataModelDynamicViewModel(Module module, ISettingsService settingsService, IDataModelUIService dataModelUIService) { _module = module; _dataModelUIService = dataModelUIService; @@ -39,6 +43,9 @@ namespace Artemis.UI.Shared.Input Initialize(); } + /// + /// Gets or sets the brush to use for the input button + /// public SolidColorBrush ButtonBrush { get => _buttonBrush; @@ -49,26 +56,41 @@ namespace Artemis.UI.Shared.Input } } + /// + /// Gets the brush to use for the switch button + /// public SolidColorBrush SwitchButtonBrush => new SolidColorBrush(ButtonBrush.Color.Darken()); + /// + /// Gets or sets the placeholder text when no value is entered + /// public string Placeholder { get => _placeholder; set => SetAndNotify(ref _placeholder, value); } + /// + /// Gets or sets the enabled state of the input + /// public bool IsEnabled { get => _isEnabled; set => SetAndNotify(ref _isEnabled, value); } + /// + /// Gets or sets whether the switch button should be displayed + /// public bool DisplaySwitchButton { get => _displaySwitchButton; set => SetAndNotify(ref _displaySwitchButton, value); } + /// + /// Gets or sets the types of properties this view model will allow to be selected + /// public Type[] FilterTypes { get => _filterTypes; @@ -79,18 +101,38 @@ namespace Artemis.UI.Shared.Input } } + /// + /// Gets a bindable collection of extra data model view models to show + /// public BindableCollection ExtraDataModelViewModels { get; } + + /// + /// Gets a boolean indicating whether there are any extra data models + /// public bool HasExtraDataModels => ExtraDataModelViewModels.Any(); + /// + /// Command used by view + /// public DelegateCommand SelectPropertyCommand { get; } + + /// + /// Setting used by view + /// public PluginSetting ShowDataModelValues { get; } - public DataModelPropertiesViewModel DataModelViewModel + /// + /// Gets or sets root the data model view model + /// + public DataModelPropertiesViewModel? DataModelViewModel { get => _dataModelViewModel; private set => SetAndNotify(ref _dataModelViewModel, value); } + /// + /// Gets or sets a boolean indicating whether the data model is open + /// public bool IsDataModelViewModelOpen { get => _isDataModelViewModelOpen; @@ -101,7 +143,10 @@ namespace Artemis.UI.Shared.Input } } - public DataModelPath DataModelPath + /// + /// Gets or sets the data model path of the currently selected data model property + /// + public DataModelPath? DataModelPath { get => _dataModelPath; private set @@ -113,9 +158,19 @@ namespace Artemis.UI.Shared.Input } } + /// + /// Gets a boolean indicating whether the current selection is valid + /// public bool IsValid => DataModelPath?.IsValid ?? true; - public string DisplayValue => DataModelPath?.GetPropertyDescription()?.Name ?? DataModelPath?.Segments.LastOrDefault()?.Identifier; + /// + /// Gets the display name of the currently selected property + /// + public string? DisplayValue => DataModelPath?.GetPropertyDescription()?.Name ?? DataModelPath?.Segments.LastOrDefault()?.Identifier; + + /// + /// Gets the human readable path of the currently selected property + /// public string DisplayPath { get @@ -128,8 +183,16 @@ namespace Artemis.UI.Shared.Input } } + /// + /// Gets or sets a boolean indicating whether event child VMs should be loaded, defaults to + /// public bool LoadEventChildren { get; set; } = true; + /// + /// Changes the root data model VM stored in to the provided + /// + /// + /// The data model VM to set the new root data model to public void ChangeDataModel(DataModelPropertiesViewModel dataModel) { if (DataModelViewModel != null) @@ -141,12 +204,19 @@ namespace Artemis.UI.Shared.Input DataModelViewModel.UpdateRequested += DataModelOnUpdateRequested; } - public void ChangeDataModelPath(DataModelPath dataModelPath) + /// + /// Changes the currently selected property by its path + /// + /// The path of the property to set the selection to + public void ChangeDataModelPath(DataModelPath? dataModelPath) { DataModelPath?.Dispose(); DataModelPath = dataModelPath != null ? new DataModelPath(dataModelPath) : null; } + /// + /// Requests switching the input type to static using a + /// public void SwitchToStatic() { ChangeDataModelPath(null); @@ -158,13 +228,14 @@ namespace Artemis.UI.Shared.Input { // Get the data models DataModelViewModel = _dataModelUIService.GetPluginDataModelVisualization(_module, true); - DataModelViewModel.UpdateRequested += DataModelOnUpdateRequested; + if (DataModelViewModel != null) + DataModelViewModel.UpdateRequested += DataModelOnUpdateRequested; ExtraDataModelViewModels.CollectionChanged += ExtraDataModelViewModelsOnCollectionChanged; _updateTimer.Start(); _updateTimer.Elapsed += OnUpdateTimerOnElapsed; } - private void ExecuteSelectPropertyCommand(object context) + private void ExecuteSelectPropertyCommand(object? context) { if (!(context is DataModelVisualizationViewModel selected)) return; @@ -173,40 +244,61 @@ namespace Artemis.UI.Shared.Input OnPropertySelected(new DataModelInputDynamicEventArgs(DataModelPath)); } + #region IDisposable + + /// + /// Releases the unmanaged resources used by the object and optionally releases the managed resources. + /// + /// + /// to release both managed and unmanaged resources; + /// to release only unmanaged resources. + /// + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _updateTimer.Stop(); + _updateTimer.Dispose(); + _updateTimer.Elapsed -= OnUpdateTimerOnElapsed; + + DataModelPath?.Dispose(); + } + } + + /// public void Dispose() { - _updateTimer.Stop(); - _updateTimer.Dispose(); - _updateTimer.Elapsed -= OnUpdateTimerOnElapsed; - - DataModelPath?.Dispose(); + Dispose(true); + GC.SuppressFinalize(this); } + #endregion + #region Event handlers - private void DataModelOnUpdateRequested(object sender, EventArgs e) + private void DataModelOnUpdateRequested(object? sender, EventArgs e) { - DataModelViewModel.ApplyTypeFilter(true, FilterTypes); + DataModelViewModel?.ApplyTypeFilter(true, FilterTypes); foreach (DataModelPropertiesViewModel extraDataModelViewModel in ExtraDataModelViewModels) extraDataModelViewModel.ApplyTypeFilter(true, FilterTypes); } - private void ExtraDataModelViewModelsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + private void ExtraDataModelViewModelsOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { NotifyOfPropertyChange(nameof(HasExtraDataModels)); } - private void OnUpdateTimerOnElapsed(object sender, ElapsedEventArgs e) + private void OnUpdateTimerOnElapsed(object? sender, ElapsedEventArgs e) { if (!IsDataModelViewModelOpen) return; - + UpdateDataModelVisualization(); } private void UpdateDataModelVisualization() { - DataModelViewModel.Update(_dataModelUIService, new DataModelUpdateConfiguration(LoadEventChildren)); + DataModelViewModel?.Update(_dataModelUIService, new DataModelUpdateConfiguration(LoadEventChildren)); foreach (DataModelPropertiesViewModel extraDataModelViewModel in ExtraDataModelViewModels) extraDataModelViewModel.Update(_dataModelUIService, new DataModelUpdateConfiguration(LoadEventChildren)); } @@ -215,14 +307,28 @@ namespace Artemis.UI.Shared.Input #region Events - public event EventHandler PropertySelected; - public event EventHandler SwitchToStaticRequested; + /// + /// Occurs when anew property has been selected + /// + public event EventHandler? PropertySelected; + /// + /// Occurs when a switch to static input has been requested + /// + public event EventHandler? SwitchToStaticRequested; + + /// + /// Invokes the event + /// + /// protected virtual void OnPropertySelected(DataModelInputDynamicEventArgs e) { PropertySelected?.Invoke(this, e); } + /// + /// Invokes the event + /// protected virtual void OnSwitchToStaticRequested() { SwitchToStaticRequested?.Invoke(this, EventArgs.Empty); diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelStaticViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelStaticViewModel.cs index 5b632f1ce..e1db98f22 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelStaticViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelStaticViewModel.cs @@ -11,29 +11,33 @@ using Stylet; namespace Artemis.UI.Shared.Input { + /// + /// Represents a view model that allows inputting a static value used by boolean operations on a certain data model + /// property + /// public class DataModelStaticViewModel : PropertyChangedBase, IDisposable { private readonly IDataModelUIService _dataModelUIService; - private readonly Window _rootView; + private readonly Window? _rootView; private SolidColorBrush _buttonBrush = new SolidColorBrush(Color.FromRgb(171, 71, 188)); private bool _displaySwitchButton; - private DataModelDisplayViewModel _displayViewModel; - private DataModelInputViewModel _inputViewModel; + private DataModelDisplayViewModel? _displayViewModel; + private DataModelInputViewModel? _inputViewModel; private bool _isEnabled; private string _placeholder = "Enter a value"; private DataModelPropertyAttribute _targetDescription; private Type _targetType; - private object _value; + private object? _value; - public DataModelStaticViewModel(Type targetType, DataModelPropertyAttribute targetDescription, IDataModelUIService dataModelUIService) + internal DataModelStaticViewModel(Type targetType, DataModelPropertyAttribute targetDescription, IDataModelUIService dataModelUIService) { _dataModelUIService = dataModelUIService; _rootView = Application.Current.Windows.OfType().SingleOrDefault(x => x.IsActive); - TargetType = targetType; - TargetDescription = targetDescription; - IsEnabled = TargetType != null; - DisplayViewModel = _dataModelUIService.GetDataModelDisplayViewModel(TargetType ?? typeof(object), TargetDescription, true); + _targetType = targetType; + _targetDescription = targetDescription; + _isEnabled = TargetType != null; + _displayViewModel = _dataModelUIService.GetDataModelDisplayViewModel(TargetType ?? typeof(object), TargetDescription, true); if (_rootView != null) { @@ -63,7 +67,7 @@ namespace Artemis.UI.Shared.Input /// /// Gets the view model used to display the value /// - public DataModelDisplayViewModel DisplayViewModel + public DataModelDisplayViewModel? DisplayViewModel { get => _displayViewModel; private set => SetAndNotify(ref _displayViewModel, value); @@ -72,7 +76,7 @@ namespace Artemis.UI.Shared.Input /// /// Gets the view model used to edit the value /// - public DataModelInputViewModel InputViewModel + public DataModelInputViewModel? InputViewModel { get => _inputViewModel; private set => SetAndNotify(ref _inputViewModel, value); @@ -99,7 +103,7 @@ namespace Artemis.UI.Shared.Input /// /// Gets or sets the value of the target /// - public object Value + public object? Value { get => _value; set @@ -173,7 +177,7 @@ namespace Artemis.UI.Shared.Input } /// - /// Requests switching the input type to dynamic + /// Requests switching the input type to dynamic using a /// public void SwitchToDynamic() { @@ -183,7 +187,7 @@ namespace Artemis.UI.Shared.Input OnSwitchToDynamicRequested(); } - private void ApplyFreeInput(object value, bool submitted) + private void ApplyFreeInput(object? value, bool submitted) { if (submitted) OnValueUpdated(new DataModelInputStaticEventArgs(value)); @@ -204,11 +208,13 @@ namespace Artemis.UI.Shared.Input protected virtual void Dispose(bool disposing) { if (disposing) + { if (_rootView != null) { _rootView.MouseUp -= RootViewOnMouseUp; _rootView.KeyUp -= RootViewOnKeyUp; } + } } /// diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelEventViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelEventViewModel.cs index 93946c297..5fab8425c 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelEventViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelEventViewModel.cs @@ -6,21 +6,28 @@ using Artemis.UI.Shared.Services; namespace Artemis.UI.Shared { + /// + /// Represents a view model that visualizes an event data model property + /// public class DataModelEventViewModel : DataModelVisualizationViewModel { - private Type _displayValueType; + private Type? _displayValueType; internal DataModelEventViewModel(DataModel dataModel, DataModelVisualizationViewModel parent, DataModelPath dataModelPath) : base(dataModel, parent, dataModelPath) { } - public Type DisplayValueType + /// + /// Gets the type of event arguments this event triggers and that must be displayed as children + /// + public Type? DisplayValueType { get => _displayValueType; set => SetAndNotify(ref _displayValueType, value); } - public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration configuration) + /// + public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration) { DisplayValueType = DataModelPath?.GetPropertyType(); @@ -40,13 +47,16 @@ namespace Artemis.UI.Shared dataModelVisualizationViewModel.Update(dataModelUIService, configuration); } - public override object GetCurrentValue() + /// + /// Always returns for data model events + /// + public override object? GetCurrentValue() { return null; } /// - public override string ToString() + public override string? ToString() { return DisplayPath ?? Path; } diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertiesViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertiesViewModel.cs index fd038c6f6..56cd17a83 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertiesViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertiesViewModel.cs @@ -5,43 +5,63 @@ using Artemis.UI.Shared.Services; namespace Artemis.UI.Shared { + /// + /// Represents a view model that wraps a regular but contained in + /// a + /// public class DataModelListPropertiesViewModel : DataModelPropertiesViewModel { - private object _displayValue; + private readonly ListPredicateWrapperDataModel _listPredicateWrapper; + private object? _displayValue; private int _index; - private Type _listType; + private Type? _listType; - public DataModelListPropertiesViewModel(Type listType) : base(null, null, null) + internal DataModelListPropertiesViewModel(Type listType) : base(null, null, null) { - DataModel = ListPredicateWrapperDataModel.Create(listType); + _listPredicateWrapper = ListPredicateWrapperDataModel.Create(listType); + DataModel = _listPredicateWrapper; ListType = listType; } + /// + /// Gets the index of the element within the list + /// public int Index { get => _index; set => SetAndNotify(ref _index, value); } - public Type ListType + /// + /// Gets the type of elements contained in the list + /// + public Type? ListType { get => _listType; set => SetAndNotify(ref _listType, value); } - public new object DisplayValue + /// + /// Gets the value of the property that is being visualized + /// + public new object? DisplayValue { get => _displayValue; set => SetAndNotify(ref _displayValue, value); } - public DataModelVisualizationViewModel DisplayViewModel => Children.FirstOrDefault(); + /// + /// Gets the view model that handles displaying the property + /// + public DataModelVisualizationViewModel? DisplayViewModel => Children.FirstOrDefault(); - public override string DisplayPath => null; + /// + public override string? DisplayPath => null; - public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration configuration) + /// + public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration) { - ((ListPredicateWrapperDataModel) DataModel).UntypedValue = DisplayValue; + _listPredicateWrapper.UntypedValue = DisplayValue; PopulateProperties(dataModelUIService, configuration); if (DisplayViewModel == null) @@ -52,7 +72,8 @@ namespace Artemis.UI.Shared DisplayViewModel.Update(dataModelUIService, null); } - public override object GetCurrentValue() + /// + public override object? GetCurrentValue() { return DisplayValue; } diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertyViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertyViewModel.cs index 1d3479a68..cf5bed3c8 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertyViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertyViewModel.cs @@ -10,19 +10,22 @@ namespace Artemis.UI.Shared /// public class DataModelListPropertyViewModel : DataModelPropertyViewModel { + private readonly ListPredicateWrapperDataModel _listPredicateWrapper; private int _index; - private Type _listType; + private Type? _listType; internal DataModelListPropertyViewModel(Type listType, DataModelDisplayViewModel displayViewModel) : base(null, null, null) { - DataModel = ListPredicateWrapperDataModel.Create(listType); + _listPredicateWrapper = ListPredicateWrapperDataModel.Create(listType); + DataModel = _listPredicateWrapper; ListType = listType; DisplayViewModel = displayViewModel; } internal DataModelListPropertyViewModel(Type listType) : base(null, null, null) { - DataModel = ListPredicateWrapperDataModel.Create(listType); + _listPredicateWrapper = ListPredicateWrapperDataModel.Create(listType); + DataModel = _listPredicateWrapper; ListType = listType; } @@ -38,26 +41,26 @@ namespace Artemis.UI.Shared /// /// Gets the type of elements contained in the list /// - public Type ListType + public Type? ListType { get => _listType; private set => SetAndNotify(ref _listType, value); } /// - public override object GetCurrentValue() + public override object? GetCurrentValue() { return DisplayValue; } /// - public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration configuration) + public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration) { // Display value gets updated by parent, don't do anything if it is null if (DisplayValue == null) return; - ((ListPredicateWrapperDataModel) DataModel).UntypedValue = DisplayValue; + _listPredicateWrapper.UntypedValue = DisplayValue; if (DisplayViewModel == null) DisplayViewModel = dataModelUIService.GetDataModelDisplayViewModel(DisplayValue.GetType(), PropertyDescription, true); diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs index cba6df286..d590df5ca 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs @@ -13,21 +13,22 @@ namespace Artemis.UI.Shared public class DataModelListViewModel : DataModelVisualizationViewModel { private string _countDisplay; - private Type _displayValueType; - private IEnumerable _list; + private Type? _displayValueType; + private IEnumerable? _list; private BindableCollection _listChildren; private int _listCount; internal DataModelListViewModel(DataModel dataModel, DataModelVisualizationViewModel parent, DataModelPath dataModelPath) : base(dataModel, parent, dataModelPath) { - ListChildren = new BindableCollection(); + _countDisplay = "0 items"; + _listChildren = new BindableCollection(); } /// /// Gets the instance of the list that is being visualized /// - public IEnumerable List + public IEnumerable? List { get => _list; private set => SetAndNotify(ref _list, value); @@ -45,7 +46,7 @@ namespace Artemis.UI.Shared /// /// Gets the type of elements this list contains and that must be displayed as children /// - public Type DisplayValueType + public Type? DisplayValueType { get => _displayValueType; set => SetAndNotify(ref _displayValueType, value); @@ -70,7 +71,7 @@ namespace Artemis.UI.Shared } /// - public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration configuration) + public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration) { if (Parent != null && !Parent.IsVisualizationExpanded) return; @@ -86,10 +87,12 @@ namespace Artemis.UI.Shared if (item == null) continue; - DataModelVisualizationViewModel child; + DataModelVisualizationViewModel? child; if (ListChildren.Count <= index) { child = CreateListChild(dataModelUIService, item.GetType()); + if (child == null) + continue; ListChildren.Add(child); } else @@ -126,10 +129,10 @@ namespace Artemis.UI.Shared return $"[List] {DisplayPath ?? Path} - {ListCount} item(s)"; } - private DataModelVisualizationViewModel CreateListChild(IDataModelUIService dataModelUIService, Type listType) + private DataModelVisualizationViewModel? CreateListChild(IDataModelUIService dataModelUIService, Type listType) { // If a display VM was found, prefer to use that in any case - DataModelDisplayViewModel typeViewModel = dataModelUIService.GetDataModelDisplayViewModel(listType, PropertyDescription); + DataModelDisplayViewModel? typeViewModel = dataModelUIService.GetDataModelDisplayViewModel(listType, PropertyDescription); if (typeViewModel != null) return new DataModelListPropertyViewModel(listType, typeViewModel); // For primitives, create a property view model, it may be null that is fine diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertiesViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertiesViewModel.cs index 5dc784157..99092a24a 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertiesViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertiesViewModel.cs @@ -10,10 +10,10 @@ namespace Artemis.UI.Shared /// public class DataModelPropertiesViewModel : DataModelVisualizationViewModel { - private object _displayValue; - private Type _displayValueType; + private object? _displayValue; + private Type? _displayValueType; - internal DataModelPropertiesViewModel(DataModel dataModel, DataModelVisualizationViewModel parent, DataModelPath dataModelPath) + internal DataModelPropertiesViewModel(DataModel? dataModel, DataModelVisualizationViewModel? parent, DataModelPath? dataModelPath) : base(dataModel, parent, dataModelPath) { } @@ -21,7 +21,7 @@ namespace Artemis.UI.Shared /// /// Gets the type of the property that is being visualized /// - public Type DisplayValueType + public Type? DisplayValueType { get => _displayValueType; private set => SetAndNotify(ref _displayValueType, value); @@ -30,19 +30,19 @@ namespace Artemis.UI.Shared /// /// Gets the value of the property that is being visualized /// - public object DisplayValue + public object? DisplayValue { get => _displayValue; private set => SetAndNotify(ref _displayValue, value); } /// - public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration configuration) + public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration) { DisplayValueType = DataModelPath?.GetPropertyType(); // Only set a display value if ToString returns useful information and not just the type name - object currentValue = GetCurrentValue(); + object? currentValue = GetCurrentValue(); if (currentValue != null && currentValue.ToString() != currentValue.GetType().ToString()) DisplayValue = currentValue.ToString(); else @@ -60,7 +60,7 @@ namespace Artemis.UI.Shared } /// - public override object GetCurrentValue() + public override object? GetCurrentValue() { if (Parent == null || Parent.IsRootViewModel || IsRootViewModel) return DataModel; @@ -68,7 +68,7 @@ namespace Artemis.UI.Shared } /// - public override string ToString() + public override string? ToString() { return DisplayPath ?? Path; } diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertyViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertyViewModel.cs index 3c12b6270..3d3b20102 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertyViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertyViewModel.cs @@ -11,11 +11,11 @@ namespace Artemis.UI.Shared /// public class DataModelPropertyViewModel : DataModelVisualizationViewModel { - private object _displayValue; - private Type _displayValueType; - private DataModelDisplayViewModel _displayViewModel; + private object? _displayValue; + private Type? _displayValueType; + private DataModelDisplayViewModel? _displayViewModel; - internal DataModelPropertyViewModel(DataModel dataModel, DataModelVisualizationViewModel parent, DataModelPath dataModelPath) + internal DataModelPropertyViewModel(DataModel? dataModel, DataModelVisualizationViewModel? parent, DataModelPath? dataModelPath) : base(dataModel, parent, dataModelPath) { } @@ -23,7 +23,7 @@ namespace Artemis.UI.Shared /// /// Gets the value of the property that is being visualized /// - public object DisplayValue + public object? DisplayValue { get => _displayValue; internal set => SetAndNotify(ref _displayValue, value); @@ -32,7 +32,7 @@ namespace Artemis.UI.Shared /// /// Gets the type of the property that is being visualized /// - public Type DisplayValueType + public Type? DisplayValueType { get => _displayValueType; protected set => SetAndNotify(ref _displayValueType, value); @@ -41,26 +41,31 @@ namespace Artemis.UI.Shared /// /// Gets the view model used to display the display value /// - public DataModelDisplayViewModel DisplayViewModel + public DataModelDisplayViewModel? DisplayViewModel { get => _displayViewModel; internal set => SetAndNotify(ref _displayViewModel, value); } /// - public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration configuration) + public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration) { if (Parent != null && !Parent.IsVisualizationExpanded && !Parent.IsRootViewModel) return; if (DisplayViewModel == null) { - DisplayViewModel = dataModelUIService.GetDataModelDisplayViewModel(DataModelPath.GetPropertyType(), PropertyDescription, true); - DisplayViewModel.PropertyDescription = DataModelPath.GetPropertyDescription(); + Type? propertyType = DataModelPath?.GetPropertyType(); + if (propertyType != null) + { + DisplayViewModel = dataModelUIService.GetDataModelDisplayViewModel(propertyType, PropertyDescription, true); + if (DisplayViewModel != null) + DisplayViewModel.PropertyDescription = DataModelPath?.GetPropertyDescription(); + } } DisplayValue = GetCurrentValue(); - DisplayValueType = DisplayValue != null ? DisplayValue.GetType() : DataModelPath.GetPropertyType(); + DisplayValueType = DisplayValue != null ? DisplayValue.GetType() : DataModelPath?.GetPropertyType(); DisplayViewModel?.UpdateValue(DisplayValue); } diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs index 67e563bcf..55fa3d730 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs @@ -17,24 +17,24 @@ namespace Artemis.UI.Shared { private const int MaxDepth = 4; private BindableCollection _children; - private DataModel _dataModel; + private DataModel? _dataModel; private bool _isMatchingFilteredTypes; private bool _isVisualizationExpanded; - private DataModelVisualizationViewModel _parent; - private DataModelPropertyAttribute _propertyDescription; + private DataModelVisualizationViewModel? _parent; + private DataModelPropertyAttribute? _propertyDescription; - internal DataModelVisualizationViewModel(DataModel dataModel, DataModelVisualizationViewModel parent, DataModelPath dataModelPath) + internal DataModelVisualizationViewModel(DataModel? dataModel, DataModelVisualizationViewModel? parent, DataModelPath? dataModelPath) { - DataModel = dataModel; - Parent = parent; + _dataModel = dataModel; + _children = new BindableCollection(); + _parent = parent; DataModelPath = dataModelPath; - Children = new BindableCollection(); IsMatchingFilteredTypes = true; if (parent == null) IsRootViewModel = true; else - PropertyDescription = DataModelPath?.GetPropertyDescription() ?? DataModel.DataModelDescription; + PropertyDescription = DataModelPath?.GetPropertyDescription() ?? DataModel?.DataModelDescription; } /// @@ -45,12 +45,12 @@ namespace Artemis.UI.Shared /// /// Gets the data model path to the property this view model is visualizing /// - public DataModelPath DataModelPath { get; } + public DataModelPath? DataModelPath { get; } /// /// Gets a string representation of the path backing this model /// - public string Path => DataModelPath?.Path; + public string? Path => DataModelPath?.Path; /// /// Gets the property depth of the view model @@ -60,7 +60,7 @@ namespace Artemis.UI.Shared /// /// Gets the data model backing this view model /// - public DataModel DataModel + public DataModel? DataModel { get => _dataModel; protected set => SetAndNotify(ref _dataModel, value); @@ -69,7 +69,7 @@ namespace Artemis.UI.Shared /// /// Gets the property description of the property this view model is visualizing /// - public DataModelPropertyAttribute PropertyDescription + public DataModelPropertyAttribute? PropertyDescription { get => _propertyDescription; protected set => SetAndNotify(ref _propertyDescription, value); @@ -78,7 +78,7 @@ namespace Artemis.UI.Shared /// /// Gets the parent of this view model /// - public DataModelVisualizationViewModel Parent + public DataModelVisualizationViewModel? Parent { get => _parent; protected set => SetAndNotify(ref _parent, value); @@ -119,7 +119,7 @@ namespace Artemis.UI.Shared /// /// Gets a user-friendly representation of the /// - public virtual string DisplayPath => DataModelPath != null + public virtual string? DisplayPath => DataModelPath != null ? string.Join(" › ", DataModelPath.Segments.Select(s => s.GetPropertyDescription()?.Name ?? s.Identifier)) : null; @@ -128,18 +128,18 @@ namespace Artemis.UI.Shared /// /// The data model UI service used during update /// The configuration to apply while updating - public abstract void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration configuration); + public abstract void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration); /// /// Gets the current value of the property being visualized /// /// The current value of the property being visualized - public virtual object GetCurrentValue() + public virtual object? GetCurrentValue() { if (IsRootViewModel) return null; - return DataModelPath.GetValue(); + return DataModelPath?.GetValue(); } /// @@ -148,7 +148,7 @@ namespace Artemis.UI.Shared /// /// Whether the type may be a loose match, meaning it can be cast or converted /// The types to filter - public void ApplyTypeFilter(bool looseMatch, params Type[] filteredTypes) + public void ApplyTypeFilter(bool looseMatch, params Type[]? filteredTypes) { if (filteredTypes != null) { @@ -176,7 +176,7 @@ namespace Artemis.UI.Shared } // If the type couldn't be retrieved either way, assume false - Type type = DataModelPath?.GetPropertyType(); + Type? type = DataModelPath?.GetPropertyType(); if (type == null) { IsMatchingFilteredTypes = false; @@ -197,12 +197,14 @@ namespace Artemis.UI.Shared return 0; } - internal void PopulateProperties(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration dataModelUpdateConfiguration) + internal void PopulateProperties(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? dataModelUpdateConfiguration) { if (IsRootViewModel && DataModel == null) return; - Type modelType = IsRootViewModel ? DataModel.GetType() : DataModelPath.GetPropertyType(); + Type? modelType = IsRootViewModel ? DataModel?.GetType() : DataModelPath?.GetPropertyType(); + if (modelType == null) + throw new ArtemisSharedUIException("Failed to populate data model visualization properties, couldn't get a property type"); // Add missing static children foreach (PropertyInfo propertyInfo in modelType.GetProperties(BindingFlags.Public | BindingFlags.Instance).OrderBy(t => t.MetadataToken)) @@ -212,27 +214,30 @@ namespace Artemis.UI.Shared continue; if (propertyInfo.GetCustomAttribute() != null) continue; - MethodInfo getMethod = propertyInfo.GetGetMethod(); + MethodInfo? getMethod = propertyInfo.GetGetMethod(); if (getMethod == null || getMethod.GetParameters().Any()) continue; - DataModelVisualizationViewModel child = CreateChild(dataModelUIService, childPath, GetChildDepth()); + DataModelVisualizationViewModel? child = CreateChild(dataModelUIService, childPath, GetChildDepth()); if (child != null) Children.Add(child); } // Remove static children that should be hidden - ReadOnlyCollection hiddenProperties = DataModel.GetHiddenProperties(); - foreach (PropertyInfo hiddenProperty in hiddenProperties) + if (DataModel != null) { - string childPath = AppendToPath(hiddenProperty.Name); - DataModelVisualizationViewModel toRemove = Children.FirstOrDefault(c => c.Path != null && c.Path == childPath); - if (toRemove != null) - Children.Remove(toRemove); + ReadOnlyCollection hiddenProperties = DataModel.GetHiddenProperties(); + foreach (PropertyInfo hiddenProperty in hiddenProperties) + { + string childPath = AppendToPath(hiddenProperty.Name); + DataModelVisualizationViewModel? toRemove = Children.FirstOrDefault(c => c.Path != null && c.Path == childPath); + if (toRemove != null) + Children.Remove(toRemove); + } } // Add missing dynamic children - object value = Parent == null || Parent.IsRootViewModel ? DataModel : DataModelPath.GetValue(); + object? value = Parent == null || Parent.IsRootViewModel ? DataModel : DataModelPath?.GetValue(); if (value is DataModel dataModel) foreach (KeyValuePair kvp in dataModel.DynamicDataModels) { @@ -240,19 +245,21 @@ namespace Artemis.UI.Shared if (Children.Any(c => c.Path != null && c.Path.Equals(childPath))) continue; - DataModelVisualizationViewModel child = CreateChild(dataModelUIService, childPath, GetChildDepth()); + DataModelVisualizationViewModel? child = CreateChild(dataModelUIService, childPath, GetChildDepth()); if (child != null) Children.Add(child); } // Remove dynamic children that have been removed from the data model - List toRemoveDynamic = Children.Where(c => !c.DataModelPath.IsValid).ToList(); + List toRemoveDynamic = Children.Where(c => c.DataModelPath != null && !c.DataModelPath.IsValid).ToList(); if (toRemoveDynamic.Any()) Children.RemoveRange(toRemoveDynamic); } - private DataModelVisualizationViewModel CreateChild(IDataModelUIService dataModelUIService, string path, int depth) + private DataModelVisualizationViewModel? CreateChild(IDataModelUIService dataModelUIService, string path, int depth) { + if (DataModel == null) + throw new ArtemisSharedUIException("Cannot create a data model visualization child VM for a parent without a data model"); if (depth > MaxDepth) return null; @@ -260,8 +267,8 @@ namespace Artemis.UI.Shared if (!dataModelPath.IsValid) return null; - PropertyInfo propertyInfo = dataModelPath.GetPropertyInfo(); - Type propertyType = dataModelPath.GetPropertyType(); + PropertyInfo? propertyInfo = dataModelPath.GetPropertyInfo(); + Type? propertyType = dataModelPath.GetPropertyType(); // Skip properties decorated with DataModelIgnore if (propertyInfo != null && Attribute.IsDefined(propertyInfo, typeof(DataModelIgnoreAttribute))) @@ -270,8 +277,11 @@ namespace Artemis.UI.Shared if (DataModel.GetHiddenProperties().Any(p => p.Equals(propertyInfo))) return null; + if (propertyType == null) + return null; + // If a display VM was found, prefer to use that in any case - DataModelDisplayViewModel typeViewModel = dataModelUIService.GetDataModelDisplayViewModel(propertyType, PropertyDescription); + DataModelDisplayViewModel? typeViewModel = dataModelUIService.GetDataModelDisplayViewModel(propertyType, PropertyDescription); if (typeViewModel != null) return new DataModelPropertyViewModel(DataModel, this, dataModelPath) {DisplayViewModel = typeViewModel, Depth = depth}; // For primitives, create a property view model, it may be null that is fine diff --git a/src/Artemis.UI.Shared/Events/DataModelInputDynamicEventArgs.cs b/src/Artemis.UI.Shared/Events/DataModelInputDynamicEventArgs.cs index dc19cf765..e5a9806de 100644 --- a/src/Artemis.UI.Shared/Events/DataModelInputDynamicEventArgs.cs +++ b/src/Artemis.UI.Shared/Events/DataModelInputDynamicEventArgs.cs @@ -9,7 +9,7 @@ namespace Artemis.UI.Shared /// public class DataModelInputDynamicEventArgs : EventArgs { - internal DataModelInputDynamicEventArgs(DataModelPath dataModelPath) + internal DataModelInputDynamicEventArgs(DataModelPath? dataModelPath) { DataModelPath = dataModelPath; } @@ -17,6 +17,6 @@ namespace Artemis.UI.Shared /// /// Gets the data model path that was selected /// - public DataModelPath DataModelPath { get; } + public DataModelPath? DataModelPath { get; } } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Events/DataModelInputStaticEventArgs.cs b/src/Artemis.UI.Shared/Events/DataModelInputStaticEventArgs.cs index 7c5f8f4ef..650b558b8 100644 --- a/src/Artemis.UI.Shared/Events/DataModelInputStaticEventArgs.cs +++ b/src/Artemis.UI.Shared/Events/DataModelInputStaticEventArgs.cs @@ -8,7 +8,7 @@ namespace Artemis.UI.Shared /// public class DataModelInputStaticEventArgs : EventArgs { - internal DataModelInputStaticEventArgs(object value) + internal DataModelInputStaticEventArgs(object? value) { Value = value; } @@ -16,6 +16,6 @@ namespace Artemis.UI.Shared /// /// The value that was submitted /// - public object Value { get; } + public object? Value { get; } } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Events/ProfileEventArgs.cs b/src/Artemis.UI.Shared/Events/ProfileEventArgs.cs index 1f3212d02..95fe4aba3 100644 --- a/src/Artemis.UI.Shared/Events/ProfileEventArgs.cs +++ b/src/Artemis.UI.Shared/Events/ProfileEventArgs.cs @@ -8,12 +8,12 @@ namespace Artemis.UI.Shared /// public class ProfileEventArgs : EventArgs { - internal ProfileEventArgs(Profile profile) + internal ProfileEventArgs(Profile? profile) { Profile = profile; } - internal ProfileEventArgs(Profile profile, Profile previousProfile) + internal ProfileEventArgs(Profile? profile, Profile? previousProfile) { Profile = profile; PreviousProfile = previousProfile; @@ -22,7 +22,7 @@ namespace Artemis.UI.Shared /// /// Gets the profile the event was raised for /// - public Profile Profile { get; } + public Profile? Profile { get; } /// /// If applicable, the previous active profile before the event was raised diff --git a/src/Artemis.UI.Shared/Events/RenderProfileElementEventArgs.cs b/src/Artemis.UI.Shared/Events/RenderProfileElementEventArgs.cs index 0e70b7be9..e2555f3ab 100644 --- a/src/Artemis.UI.Shared/Events/RenderProfileElementEventArgs.cs +++ b/src/Artemis.UI.Shared/Events/RenderProfileElementEventArgs.cs @@ -8,12 +8,12 @@ namespace Artemis.UI.Shared /// public class RenderProfileElementEventArgs : EventArgs { - internal RenderProfileElementEventArgs(RenderProfileElement renderProfileElement) + internal RenderProfileElementEventArgs(RenderProfileElement? renderProfileElement) { RenderProfileElement = renderProfileElement; } - internal RenderProfileElementEventArgs(RenderProfileElement renderProfileElement, RenderProfileElement previousRenderProfileElement) + internal RenderProfileElementEventArgs(RenderProfileElement? renderProfileElement, RenderProfileElement? previousRenderProfileElement) { RenderProfileElement = renderProfileElement; PreviousRenderProfileElement = previousRenderProfileElement; @@ -22,7 +22,7 @@ namespace Artemis.UI.Shared /// /// Gets the profile element the event was raised for /// - public RenderProfileElement RenderProfileElement { get; } + public RenderProfileElement? RenderProfileElement { get; } /// /// If applicable, the previous active profile element before the event was raised diff --git a/src/Artemis.UI.Shared/Plugins/LayerBrushes/LayerBrushConfigurationDialog.cs b/src/Artemis.UI.Shared/Plugins/LayerBrushes/LayerBrushConfigurationDialog.cs index ed20b670b..e792dbe75 100644 --- a/src/Artemis.UI.Shared/Plugins/LayerBrushes/LayerBrushConfigurationDialog.cs +++ b/src/Artemis.UI.Shared/Plugins/LayerBrushes/LayerBrushConfigurationDialog.cs @@ -15,11 +15,6 @@ namespace Artemis.UI.Shared.LayerBrushes /// public abstract class LayerBrushConfigurationDialog : ILayerBrushConfigurationDialog { - /// - /// The layer brush this dialog belongs to - /// - internal BaseLayerBrush LayerBrush { get; set; } - /// /// The type of view model the tab contains /// diff --git a/src/Artemis.UI.Shared/Plugins/LayerEffects/LayerEffectConfigurationDialog.cs b/src/Artemis.UI.Shared/Plugins/LayerEffects/LayerEffectConfigurationDialog.cs index 604214b8c..81bcdb2af 100644 --- a/src/Artemis.UI.Shared/Plugins/LayerEffects/LayerEffectConfigurationDialog.cs +++ b/src/Artemis.UI.Shared/Plugins/LayerEffects/LayerEffectConfigurationDialog.cs @@ -15,10 +15,11 @@ namespace Artemis.UI.Shared.LayerEffects /// public abstract class LayerEffectConfigurationDialog : ILayerEffectConfigurationDialog { + // TODO: See if this is still in use /// /// The layer effect this dialog belongs to /// - internal BaseLayerEffect LayerEffect { get; set; } + public BaseLayerEffect? LayerEffect { get; set; } /// /// The type of view model the tab contains diff --git a/src/Artemis.UI.Shared/Plugins/PluginConfigurationDialog.cs b/src/Artemis.UI.Shared/Plugins/PluginConfigurationDialog.cs index 7c7e62886..23f1fd0ed 100644 --- a/src/Artemis.UI.Shared/Plugins/PluginConfigurationDialog.cs +++ b/src/Artemis.UI.Shared/Plugins/PluginConfigurationDialog.cs @@ -15,11 +15,6 @@ namespace Artemis.UI.Shared /// public abstract class PluginConfigurationDialog : IPluginConfigurationDialog { - /// - /// The layer brush this dialog belongs to - /// - internal PluginFeature PluginFeature { get; set; } - /// /// The type of view model the tab contains /// diff --git a/src/Artemis.UI.Shared/Properties/Annotations.cs b/src/Artemis.UI.Shared/Properties/Annotations.cs index d2899bae9..227caf7b8 100644 --- a/src/Artemis.UI.Shared/Properties/Annotations.cs +++ b/src/Artemis.UI.Shared/Properties/Annotations.cs @@ -22,9 +22,10 @@ SOFTWARE. */ using System; -// ReSharper disable InheritdocConsiderUsage - +#pragma warning disable 8618 #pragma warning disable 1591 + +// ReSharper disable InheritdocConsiderUsage // ReSharper disable UnusedMember.Global // ReSharper disable MemberCanBePrivate.Global // ReSharper disable UnusedAutoPropertyAccessor.Global diff --git a/src/Artemis.UI.Shared/PropertyInput/PropertyInputViewModel.cs b/src/Artemis.UI.Shared/PropertyInput/PropertyInputViewModel.cs index 28ea1f1e6..d6b4909bb 100644 --- a/src/Artemis.UI.Shared/PropertyInput/PropertyInputViewModel.cs +++ b/src/Artemis.UI.Shared/PropertyInput/PropertyInputViewModel.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using Artemis.Core; using Artemis.UI.Shared.Services; using Stylet; @@ -12,7 +13,7 @@ namespace Artemis.UI.Shared public abstract class PropertyInputViewModel : PropertyInputViewModel { private bool _inputDragging; - private T _inputValue; + [AllowNull] private T _inputValue = default!; /// /// Creates a new instance of the class @@ -73,6 +74,7 @@ namespace Artemis.UI.Shared /// /// Gets or sets the input value /// + [AllowNull] public T InputValue { get => _inputValue; diff --git a/src/Artemis.UI.Shared/Screens/Dialogs/ConfirmDialogViewModel.cs b/src/Artemis.UI.Shared/Screens/Dialogs/ConfirmDialogViewModel.cs index 5b75224c5..4d65fe81c 100644 --- a/src/Artemis.UI.Shared/Screens/Dialogs/ConfirmDialogViewModel.cs +++ b/src/Artemis.UI.Shared/Screens/Dialogs/ConfirmDialogViewModel.cs @@ -19,13 +19,13 @@ namespace Artemis.UI.Shared.Screens.Dialogs public void Confirm() { - if (!Session.IsEnded) + if (Session != null && !Session.IsEnded) Session.Close(true); } public void Cancel() { - if (!Session.IsEnded) + if (Session != null && !Session.IsEnded) Session.Close(false); } } diff --git a/src/Artemis.UI.Shared/Screens/Exceptions/ExceptionViewModel.cs b/src/Artemis.UI.Shared/Screens/Exceptions/ExceptionViewModel.cs index 4945156f2..813778370 100644 --- a/src/Artemis.UI.Shared/Screens/Exceptions/ExceptionViewModel.cs +++ b/src/Artemis.UI.Shared/Screens/Exceptions/ExceptionViewModel.cs @@ -9,12 +9,12 @@ namespace Artemis.UI.Shared.Screens.Exceptions { private List _exceptions; - public ExceptionViewModel(string message, Exception exception) + public ExceptionViewModel(string message, Exception? exception) { Header = message; - Exceptions = new List(); + _exceptions = new List(); - Exception currentException = exception; + Exception? currentException = exception; while (currentException != null) { Exceptions.Add(new DialogException(currentException)); diff --git a/src/Artemis.UI.Shared/Screens/GradientEditor/ColorStopViewModel.cs b/src/Artemis.UI.Shared/Screens/GradientEditor/ColorStopViewModel.cs index 99a7f9cf2..7d93d868a 100644 --- a/src/Artemis.UI.Shared/Screens/GradientEditor/ColorStopViewModel.cs +++ b/src/Artemis.UI.Shared/Screens/GradientEditor/ColorStopViewModel.cs @@ -59,7 +59,7 @@ namespace Artemis.UI.Shared.Screens.GradientEditor set => SetAndNotify(ref _willRemoveColorStop, value); } - private void ColorStopOnPropertyChanged(object sender, PropertyChangedEventArgs e) + private void ColorStopOnPropertyChanged(object? sender, PropertyChangedEventArgs e) { _gradientEditorViewModel.ColorGradient.OnColorValuesUpdated(); } @@ -90,7 +90,7 @@ namespace Artemis.UI.Shared.Screens.GradientEditor if (!((IInputElement) sender).IsMouseCaptured) return; - Canvas parent = VisualTreeUtilities.FindParent((DependencyObject) sender, null); + Canvas? parent = VisualTreeUtilities.FindParent((DependencyObject) sender, null); Point position = e.GetPosition(parent); if (position.Y > 50) { @@ -103,8 +103,8 @@ namespace Artemis.UI.Shared.Screens.GradientEditor double minValue = 0.0; double maxValue = _gradientEditorViewModel.PreviewWidth; List stops = _gradientEditorViewModel.ColorGradient.Stops.OrderBy(s => s.Position).ToList(); - ColorGradientStop previous = stops.IndexOf(ColorStop) >= 1 ? stops[stops.IndexOf(ColorStop) - 1] : null; - ColorGradientStop next = stops.IndexOf(ColorStop) + 1 < stops.Count ? stops[stops.IndexOf(ColorStop) + 1] : null; + ColorGradientStop? previous = stops.IndexOf(ColorStop) >= 1 ? stops[stops.IndexOf(ColorStop) - 1] : null; + ColorGradientStop? next = stops.IndexOf(ColorStop) + 1 < stops.Count ? stops[stops.IndexOf(ColorStop) + 1] : null; if (previous != null) minValue = previous.Position * _gradientEditorViewModel.PreviewWidth; if (next != null) diff --git a/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorViewModel.cs b/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorViewModel.cs index ebfdfe4ad..c2cec49dd 100644 --- a/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorViewModel.cs +++ b/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorViewModel.cs @@ -14,7 +14,7 @@ namespace Artemis.UI.Shared.Screens.GradientEditor { private readonly List _originalStops; private double _previewWidth; - private ColorStopViewModel _selectedColorStopViewModel; + private ColorStopViewModel? _selectedColorStopViewModel; public GradientEditorViewModel(ColorGradient colorGradient) { @@ -28,7 +28,7 @@ namespace Artemis.UI.Shared.Screens.GradientEditor public BindableCollection ColorStopViewModels { get; set; } - public ColorStopViewModel SelectedColorStopViewModel + public ColorStopViewModel? SelectedColorStopViewModel { get => _selectedColorStopViewModel; set @@ -50,7 +50,7 @@ namespace Artemis.UI.Shared.Screens.GradientEditor public void AddColorStop(object sender, MouseEventArgs e) { - Canvas child = VisualTreeUtilities.FindChild((DependencyObject) sender, null); + Canvas? child = VisualTreeUtilities.FindChild((DependencyObject) sender, null); float position = (float) (e.GetPosition(child).X / PreviewWidth); ColorGradientStop stop = new ColorGradientStop(ColorGradient.GetColor(position), position); ColorGradient.Stops.Add(stop); @@ -74,11 +74,11 @@ namespace Artemis.UI.Shared.Screens.GradientEditor public Point GetPositionInPreview(object sender, MouseEventArgs e) { - Canvas parent = VisualTreeUtilities.FindParent((DependencyObject) sender, null); + Canvas? parent = VisualTreeUtilities.FindParent((DependencyObject) sender, null); return e.GetPosition(parent); } - public void SelectColorStop(ColorStopViewModel colorStopViewModel) + public void SelectColorStop(ColorStopViewModel? colorStopViewModel) { SelectedColorStopViewModel = colorStopViewModel; foreach (ColorStopViewModel stopViewModel in ColorStopViewModels) @@ -87,7 +87,7 @@ namespace Artemis.UI.Shared.Screens.GradientEditor public void Confirm() { - if (!Session.IsEnded) + if (Session != null && !Session.IsEnded) Session.Close(true); } @@ -98,11 +98,11 @@ namespace Artemis.UI.Shared.Screens.GradientEditor ColorGradient.Stops.AddRange(_originalStops); ColorGradient.OnColorValuesUpdated(); - if (!Session.IsEnded) + if (Session != null && !Session.IsEnded) Session.Close(false); } - private void UpdateColorStopViewModels(object sender, PropertyChangedEventArgs e) + private void UpdateColorStopViewModels(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName != nameof(PreviewWidth)) return; foreach (ColorGradientStop colorStop in ColorGradient.Stops.OrderBy(s => s.Position)) diff --git a/src/Artemis.UI.Shared/Services/ColorPickerService.cs b/src/Artemis.UI.Shared/Services/ColorPickerService.cs index 14c23aa7b..5d1f5cf34 100644 --- a/src/Artemis.UI.Shared/Services/ColorPickerService.cs +++ b/src/Artemis.UI.Shared/Services/ColorPickerService.cs @@ -56,7 +56,7 @@ namespace Artemis.UI.Shared.Services _overlayColor = new SKColor(color.R, color.G, color.B, color.A); } - private void RenderColorPickerOverlay(object sender, FrameRenderingEventArgs e) + private void RenderColorPickerOverlay(object? sender, FrameRenderingEventArgs e) { if (_mustRenderOverlay) _overlayOpacity += 0.2f; diff --git a/src/Artemis.UI.Shared/Services/DataModelUIService.cs b/src/Artemis.UI.Shared/Services/DataModelUIService.cs index 58627d3fd..4eb9b4dd7 100644 --- a/src/Artemis.UI.Shared/Services/DataModelUIService.cs +++ b/src/Artemis.UI.Shared/Services/DataModelUIService.cs @@ -46,7 +46,9 @@ namespace Artemis.UI.Shared.Services public void UpdateModules(DataModelPropertiesViewModel mainDataModelVisualization) { - List disabledChildren = mainDataModelVisualization.Children.Where(d => !d.DataModel.Feature.IsEnabled).ToList(); + List disabledChildren = mainDataModelVisualization.Children + .Where(d => d.DataModel != null && !d.DataModel.Feature.IsEnabled) + .ToList(); foreach (DataModelVisualizationViewModel child in disabledChildren) mainDataModelVisualization.Children.Remove(child); @@ -63,24 +65,24 @@ namespace Artemis.UI.Shared.Services mainDataModelVisualization.Update(this, null); } - public DataModelPropertiesViewModel GetPluginDataModelVisualization(PluginFeature pluginFeature, bool includeMainDataModel) + public DataModelPropertiesViewModel? GetPluginDataModelVisualization(PluginFeature pluginFeature, bool includeMainDataModel) { if (includeMainDataModel) { DataModelPropertiesViewModel mainDataModel = GetMainDataModelVisualization(); // If the main data model already includes the plugin data model we're done - if (mainDataModel.Children.Any(c => c.DataModel.Feature == pluginFeature)) + if (mainDataModel.Children.Any(c => c.DataModel?.Feature == pluginFeature)) return mainDataModel; // Otherwise get just the plugin data model and add it - DataModelPropertiesViewModel pluginDataModel = GetPluginDataModelVisualization(pluginFeature, false); + DataModelPropertiesViewModel? pluginDataModel = GetPluginDataModelVisualization(pluginFeature, false); if (pluginDataModel != null) mainDataModel.Children.Add(pluginDataModel); return mainDataModel; } - DataModel dataModel = _dataModelService.GetPluginDataModel(pluginFeature); + DataModel? dataModel = _dataModelService.GetPluginDataModel(pluginFeature); if (dataModel == null) return null; @@ -93,15 +95,14 @@ namespace Artemis.UI.Shared.Services return viewModel; } - public DataModelVisualizationRegistration RegisterDataModelInput(Plugin plugin, IReadOnlyCollection compatibleConversionTypes = null) where T : DataModelInputViewModel + public DataModelVisualizationRegistration RegisterDataModelInput(Plugin plugin, IReadOnlyCollection? compatibleConversionTypes = null) where T : DataModelInputViewModel { - if (compatibleConversionTypes == null) - compatibleConversionTypes = new List(); + compatibleConversionTypes ??= new List(); Type viewModelType = typeof(T); lock (_registeredDataModelEditors) { - Type supportedType = viewModelType.BaseType.GetGenericArguments()[0]; - DataModelVisualizationRegistration existing = _registeredDataModelEditors.FirstOrDefault(r => r.SupportedType == supportedType); + Type supportedType = viewModelType.BaseType!.GetGenericArguments()[0]; + DataModelVisualizationRegistration? existing = _registeredDataModelEditors.FirstOrDefault(r => r.SupportedType == supportedType); if (existing != null) { if (existing.Plugin != plugin) @@ -129,8 +130,8 @@ namespace Artemis.UI.Shared.Services Type viewModelType = typeof(T); lock (_registeredDataModelDisplays) { - Type supportedType = viewModelType.BaseType.GetGenericArguments()[0]; - DataModelVisualizationRegistration existing = _registeredDataModelDisplays.FirstOrDefault(r => r.SupportedType == supportedType); + Type supportedType = viewModelType.BaseType!.GetGenericArguments()[0]; + DataModelVisualizationRegistration? existing = _registeredDataModelDisplays.FirstOrDefault(r => r.SupportedType == supportedType); if (existing != null) { if (existing.Plugin != plugin) @@ -174,15 +175,21 @@ namespace Artemis.UI.Shared.Services } } - public DataModelDisplayViewModel GetDataModelDisplayViewModel(Type propertyType, DataModelPropertyAttribute description, bool fallBackToDefault) + public DataModelDisplayViewModel? GetDataModelDisplayViewModel(Type propertyType, DataModelPropertyAttribute? description, bool fallBackToDefault) { lock (_registeredDataModelDisplays) { - DataModelDisplayViewModel result; + DataModelDisplayViewModel? result; - DataModelVisualizationRegistration match = _registeredDataModelDisplays.FirstOrDefault(d => d.SupportedType == propertyType); + DataModelVisualizationRegistration? match = _registeredDataModelDisplays.FirstOrDefault(d => d.SupportedType == propertyType); if (match != null) + { + // If this ever happens something is likely wrong with the plugin unload detection + if (match.Plugin.Kernel == null) + throw new ArtemisSharedUIException("Cannot GetDataModelDisplayViewModel for a registration by an uninitialized plugin"); + result = (DataModelDisplayViewModel) match.Plugin.Kernel.Get(match.ViewModelType); + } else if (!fallBackToDefault) result = null; else @@ -195,15 +202,15 @@ namespace Artemis.UI.Shared.Services } } - public DataModelInputViewModel GetDataModelInputViewModel(Type propertyType, DataModelPropertyAttribute description, object initialValue, Action updateCallback) + public DataModelInputViewModel? GetDataModelInputViewModel(Type propertyType, DataModelPropertyAttribute? description, object? initialValue, Action updateCallback) { lock (_registeredDataModelEditors) { // Prefer a VM that natively supports the type - DataModelVisualizationRegistration match = _registeredDataModelEditors.FirstOrDefault(d => d.SupportedType == propertyType); + DataModelVisualizationRegistration? match = _registeredDataModelEditors.FirstOrDefault(d => d.SupportedType == propertyType); // Fall back on a VM that supports the type through conversion if (match == null) - match = _registeredDataModelEditors.FirstOrDefault(d => d.CompatibleConversionTypes.Contains(propertyType)); + match = _registeredDataModelEditors.FirstOrDefault(d => d.CompatibleConversionTypes != null && d.CompatibleConversionTypes.Contains(propertyType)); // Lastly try getting an enum VM if the provided type is an enum if (match == null && propertyType.IsEnum) match = _registeredDataModelEditors.FirstOrDefault(d => d.SupportedType == typeof(Enum)); @@ -229,7 +236,7 @@ namespace Artemis.UI.Shared.Services return _dataModelVmFactory.DataModelStaticViewModel(targetType, targetDescription); } - private DataModelInputViewModel InstantiateDataModelInputViewModel(DataModelVisualizationRegistration registration, DataModelPropertyAttribute description, object initialValue) + private DataModelInputViewModel InstantiateDataModelInputViewModel(DataModelVisualizationRegistration registration, DataModelPropertyAttribute? description, object? initialValue) { // This assumes the type can be converted, that has been checked when the VM was created if (initialValue != null && initialValue.GetType() != registration.SupportedType) @@ -240,6 +247,11 @@ namespace Artemis.UI.Shared.Services new ConstructorArgument("targetDescription", description), new ConstructorArgument("initialValue", initialValue) }; + + // If this ever happens something is likely wrong with the plugin unload detection + if (registration.Plugin.Kernel == null) + throw new ArtemisSharedUIException("Cannot InstantiateDataModelInputViewModel for a registration by an uninitialized plugin"); + DataModelInputViewModel viewModel = (DataModelInputViewModel) registration.Plugin.Kernel.Get(registration.ViewModelType, parameters); viewModel.CompatibleConversionTypes = registration.CompatibleConversionTypes; return viewModel; diff --git a/src/Artemis.UI.Shared/Services/Dialog/DialogService.cs b/src/Artemis.UI.Shared/Services/Dialog/DialogService.cs index a62dcf014..cb6748fff 100644 --- a/src/Artemis.UI.Shared/Services/Dialog/DialogService.cs +++ b/src/Artemis.UI.Shared/Services/Dialog/DialogService.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; -using System.Reflection; using System.Threading.Tasks; using System.Windows; using Artemis.Core; @@ -16,13 +14,12 @@ using Stylet; namespace Artemis.UI.Shared.Services { - // TODO: Become plugin-aware and use plugin kernel if injected into a plugin internal class DialogService : IDialogService { private readonly IKernel _kernel; + private readonly IPluginManagementService _pluginManagementService; private readonly IViewManager _viewManager; private readonly IWindowManager _windowManager; - private readonly IPluginManagementService _pluginManagementService; public DialogService(IKernel kernel, IViewManager viewManager, IWindowManager windowManager, IPluginManagementService pluginManagementService) { @@ -35,12 +32,12 @@ namespace Artemis.UI.Shared.Services private async Task ShowDialog(IParameter[] parameters) where T : DialogViewModelBase { if (parameters == null) throw new ArgumentNullException(nameof(parameters)); - return await ShowDialog("RootDialog", _kernel.Get(parameters)); + return await ShowDialog("RootDialog", GetBestKernel().Get(parameters)); } - private async Task ShowDialog(string identifier, DialogViewModelBase viewModel) + private async Task ShowDialog(string? identifier, DialogViewModelBase viewModel) { - Task result = null; + Task? result = null; await Execute.OnUIThreadAsync(() => { UIElement view = _viewManager.CreateViewForModel(viewModel); @@ -52,12 +49,22 @@ namespace Artemis.UI.Shared.Services result = DialogHost.Show(view, identifier, viewModel.OnDialogOpened, viewModel.OnDialogClosed); }); + if (result == null) + throw new ArtemisSharedUIException("Failed to show dialog host"); return await result; } + private async Task ShowDialogAt(string identifier, IParameter[] parameters) where T : DialogViewModelBase + { + if (parameters == null) + throw new ArgumentNullException(nameof(parameters)); + return await ShowDialog(identifier, GetBestKernel().Get(parameters)); + } + public async Task ShowConfirmDialog(string header, string text, string confirmText = "Confirm", string cancelText = "Cancel") { - IParameter[] arguments = { + IParameter[] arguments = + { new ConstructorArgument("header", header), new ConstructorArgument("text", text), new ConstructorArgument("confirmText", confirmText.ToUpper()), @@ -69,7 +76,9 @@ namespace Artemis.UI.Shared.Services public async Task ShowConfirmDialogAt(string identifier, string header, string text, string confirmText = "Confirm", string cancelText = "Cancel") { - IParameter[] arguments = { + if (identifier == null) throw new ArgumentNullException(nameof(identifier)); + IParameter[] arguments = + { new ConstructorArgument("header", header), new ConstructorArgument("text", text), new ConstructorArgument("confirmText", confirmText.ToUpper()), @@ -81,7 +90,7 @@ namespace Artemis.UI.Shared.Services public async Task ShowDialog() where T : DialogViewModelBase { - return await ShowDialog("RootDialog", _kernel.Get()); + return await ShowDialog("RootDialog", GetBestKernel().Get()); } public Task ShowDialog(Dictionary parameters) where T : DialogViewModelBase @@ -95,32 +104,28 @@ namespace Artemis.UI.Shared.Services public async Task ShowDialogAt(string identifier) where T : DialogViewModelBase { - return await ShowDialog(identifier, _kernel.Get()); + if (identifier == null) throw new ArgumentNullException(nameof(identifier)); + return await ShowDialog(identifier, GetBestKernel().Get()); } public async Task ShowDialogAt(string identifier, Dictionary parameters) where T : DialogViewModelBase { - if (parameters == null) - throw new ArgumentNullException(nameof(parameters)); + if (identifier == null) throw new ArgumentNullException(nameof(identifier)); + if (parameters == null) throw new ArgumentNullException(nameof(parameters)); IParameter[] paramsArray = parameters.Select(kv => new ConstructorArgument(kv.Key, kv.Value)).Cast().ToArray(); return await ShowDialogAt(identifier, paramsArray); } - private async Task ShowDialogAt(string identifier, IParameter[] parameters) where T : DialogViewModelBase - { - Plugin callingPlugin = _pluginManagementService.GetCallingPlugin(); - if (parameters == null) - throw new ArgumentNullException(nameof(parameters)); - - if (callingPlugin != null) - return await ShowDialog(identifier, callingPlugin.Kernel.Get(parameters)); - return await ShowDialog(identifier, _kernel.Get(parameters)); - } - public void ShowExceptionDialog(string message, Exception exception) { _windowManager.ShowDialog(new ExceptionViewModel(message, exception)); } + + private IKernel GetBestKernel() + { + Plugin? callingPlugin = _pluginManagementService.GetCallingPlugin(); + return callingPlugin?.Kernel ?? _kernel; + } } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/Dialog/DialogViewModelHost.cs b/src/Artemis.UI.Shared/Services/Dialog/DialogViewModelHost.cs index a31b561a5..89f4188b4 100644 --- a/src/Artemis.UI.Shared/Services/Dialog/DialogViewModelHost.cs +++ b/src/Artemis.UI.Shared/Services/Dialog/DialogViewModelHost.cs @@ -13,7 +13,7 @@ namespace Artemis.UI.Shared.Services _viewManager = viewManager; } - public DialogViewModelBase ActiveDialogViewModel { get; set; } + public DialogViewModelBase? ActiveDialogViewModel { get; set; } public bool IsOpen { get; set; } public void OpenDialog(DialogViewModelBase viewModel, string dialogIdentifier) diff --git a/src/Artemis.UI.Shared/Services/Interfaces/IDataModelUIService.cs b/src/Artemis.UI.Shared/Services/Interfaces/IDataModelUIService.cs index de53e6b7c..18ba8dd42 100644 --- a/src/Artemis.UI.Shared/Services/Interfaces/IDataModelUIService.cs +++ b/src/Artemis.UI.Shared/Services/Interfaces/IDataModelUIService.cs @@ -37,7 +37,7 @@ namespace Artemis.UI.Shared.Services /// The plugin feature to create hte data model visualization view model for /// Whether or not also to include the main data model /// A data model visualization view model containing the data model of the provided feature - DataModelPropertiesViewModel GetPluginDataModelVisualization(PluginFeature pluginFeature, bool includeMainDataModel); + DataModelPropertiesViewModel? GetPluginDataModelVisualization(PluginFeature pluginFeature, bool includeMainDataModel); /// /// Updates the children of the provided main data model visualization, removing disabled children and adding newly @@ -89,7 +89,7 @@ namespace Artemis.UI.Shared.Services /// returned if nothing else is found /// /// The most appropriate display view model for the provided - DataModelDisplayViewModel GetDataModelDisplayViewModel(Type propertyType, DataModelPropertyAttribute description, bool fallBackToDefault = false); + DataModelDisplayViewModel? GetDataModelDisplayViewModel(Type propertyType, DataModelPropertyAttribute? description, bool fallBackToDefault = false); /// /// Creates the most appropriate input view model for the provided that allows @@ -100,7 +100,7 @@ namespace Artemis.UI.Shared.Services /// The initial value to show in the input /// A function to call whenever the input was updated (submitted or not) /// The most appropriate input view model for the provided - DataModelInputViewModel GetDataModelInputViewModel(Type propertyType, DataModelPropertyAttribute description, object initialValue, Action updateCallback); + DataModelInputViewModel? GetDataModelInputViewModel(Type propertyType, DataModelPropertyAttribute? description, object? initialValue, Action updateCallback); /// /// Creates a view model that allows selecting a value from the data model diff --git a/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs b/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs index 60be21a6f..fda32c11f 100644 --- a/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs @@ -14,17 +14,17 @@ namespace Artemis.UI.Shared.Services /// /// Gets the currently selected profile /// - Profile SelectedProfile { get; } + Profile? SelectedProfile { get; } /// /// Gets the currently selected profile element /// - RenderProfileElement SelectedProfileElement { get; } + RenderProfileElement? SelectedProfileElement { get; } /// /// Gets the currently selected data binding property /// - ILayerProperty SelectedDataBinding { get; } + ILayerProperty? SelectedDataBinding { get; } /// /// Gets or sets the current time @@ -45,7 +45,7 @@ namespace Artemis.UI.Shared.Services /// Changes the selected profile /// /// The profile to select - void ChangeSelectedProfile(Profile profile); + void ChangeSelectedProfile(Profile? profile); /// /// Updates the selected profile and saves it to persistent storage @@ -56,7 +56,7 @@ namespace Artemis.UI.Shared.Services /// Changes the selected profile element /// /// The profile element to select - void ChangeSelectedProfileElement(RenderProfileElement profileElement); + void ChangeSelectedProfileElement(RenderProfileElement? profileElement); /// /// Updates the selected profile element and saves the profile it is contained in to persistent storage @@ -67,7 +67,7 @@ namespace Artemis.UI.Shared.Services /// Changes the selected data binding property /// /// The data binding property to select - void ChangeSelectedDataBinding(ILayerProperty layerProperty); + void ChangeSelectedDataBinding(ILayerProperty? layerProperty); /// /// Updates the profile preview, forcing UI-elements to re-render @@ -90,7 +90,7 @@ namespace Artemis.UI.Shared.Services /// Gets the current module the profile editor is initialized for /// /// The current module the profile editor is initialized for - ProfileModule GetCurrentModule(); + ProfileModule? GetCurrentModule(); /// /// Occurs when a new profile is selected @@ -173,6 +173,6 @@ namespace Artemis.UI.Shared.Services /// If a matching registration is found, creates a new supporting /// /// - PropertyInputViewModel CreatePropertyInputViewModel(LayerProperty layerProperty); + PropertyInputViewModel? CreatePropertyInputViewModel(LayerProperty layerProperty); } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs index a93fb24de..d7fb126d0 100644 --- a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs @@ -22,7 +22,7 @@ namespace Artemis.UI.Shared.Services private readonly object _selectedProfileLock = new object(); private TimeSpan _currentTime; private int _pixelsPerSecond; - private IKernel _kernel; + private readonly IKernel _kernel; public ProfileEditorService(IProfileService profileService, IKernel kernel, ILogger logger, ICoreService coreService) { @@ -42,9 +42,9 @@ namespace Artemis.UI.Shared.Services } public ReadOnlyCollection RegisteredPropertyEditors => _registeredPropertyEditors.AsReadOnly(); - public Profile SelectedProfile { get; private set; } - public RenderProfileElement SelectedProfileElement { get; private set; } - public ILayerProperty SelectedDataBinding { get; private set; } + public Profile? SelectedProfile { get; private set; } + public RenderProfileElement? SelectedProfileElement { get; private set; } + public ILayerProperty? SelectedDataBinding { get; private set; } public TimeSpan CurrentTime { @@ -71,7 +71,7 @@ namespace Artemis.UI.Shared.Services } } - public void ChangeSelectedProfile(Profile profile) + public void ChangeSelectedProfile(Profile? profile) { lock (_selectedProfileLock) { @@ -103,14 +103,16 @@ namespace Artemis.UI.Shared.Services lock (_selectedProfileLock) { _logger.Verbose("UpdateSelectedProfile {profile}", SelectedProfile); - _profileService.UpdateProfile(SelectedProfile, true); + if (SelectedProfile == null) + return; + _profileService.UpdateProfile(SelectedProfile, true); OnSelectedProfileChanged(new ProfileEventArgs(SelectedProfile)); UpdateProfilePreview(); } } - public void ChangeSelectedProfileElement(RenderProfileElement profileElement) + public void ChangeSelectedProfileElement(RenderProfileElement? profileElement) { lock (_selectedProfileElementLock) { @@ -131,13 +133,16 @@ namespace Artemis.UI.Shared.Services lock (_selectedProfileElementLock) { _logger.Verbose("UpdateSelectedProfileElement {profile}", SelectedProfileElement); + if (SelectedProfile == null) + return; + _profileService.UpdateProfile(SelectedProfile, true); - UpdateProfilePreview(); OnSelectedProfileElementUpdated(new RenderProfileElementEventArgs(SelectedProfileElement)); + UpdateProfilePreview(); } } - public void ChangeSelectedDataBinding(ILayerProperty layerProperty) + public void ChangeSelectedDataBinding(ILayerProperty? layerProperty) { SelectedDataBinding = layerProperty; OnSelectedDataBindingChanged(); @@ -159,6 +164,9 @@ namespace Artemis.UI.Shared.Services public bool UndoUpdateProfile() { + if (SelectedProfile == null) + return false; + bool undid = _profileService.UndoUpdateProfile(SelectedProfile); if (!undid) return false; @@ -169,6 +177,9 @@ namespace Artemis.UI.Shared.Services public bool RedoUpdateProfile() { + if (SelectedProfile == null) + return false; + bool redid = _profileService.RedoUpdateProfile(SelectedProfile); if (!redid) return false; @@ -189,7 +200,8 @@ namespace Artemis.UI.Shared.Services lock (_registeredPropertyEditors) { - Type supportedType = viewModelType.BaseType.GetGenericArguments()[0]; + // Indirectly checked if there's a BaseType above + Type supportedType = viewModelType.BaseType!.GetGenericArguments()[0]; // If the supported type is a generic, assume there is a base type if (supportedType.IsGenericParameter) { @@ -198,7 +210,7 @@ namespace Artemis.UI.Shared.Services supportedType = supportedType.BaseType; } - PropertyInputRegistration existing = _registeredPropertyEditors.FirstOrDefault(r => r.SupportedType == supportedType); + PropertyInputRegistration? existing = _registeredPropertyEditors.FirstOrDefault(r => r.SupportedType == supportedType); if (existing != null) { if (existing.Plugin != plugin) @@ -228,9 +240,9 @@ namespace Artemis.UI.Shared.Services } } - public TimeSpan SnapToTimeline(TimeSpan time, TimeSpan tolerance, bool snapToSegments, bool snapToCurrentTime, List snapTimes = null) + public TimeSpan SnapToTimeline(TimeSpan time, TimeSpan tolerance, bool snapToSegments, bool snapToCurrentTime, List? snapTimes = null) { - if (snapToSegments) + if (snapToSegments && SelectedProfileElement != null) { // Snap to the end of the start segment if (Math.Abs(time.TotalMilliseconds - SelectedProfileElement.Timeline.StartSegmentEndPosition.TotalMilliseconds) < tolerance.TotalMilliseconds) @@ -255,7 +267,7 @@ namespace Artemis.UI.Shared.Services if (snapTimes != null) { // Find the closest keyframe - TimeSpan closeSnapTime = snapTimes.FirstOrDefault(s => Math.Abs(time.TotalMilliseconds - s.TotalMilliseconds) < tolerance.TotalMilliseconds); + TimeSpan closeSnapTime = snapTimes.FirstOrDefault(s => Math.Abs(time.TotalMilliseconds - s.TotalMilliseconds) < tolerance.TotalMilliseconds)!; if (closeSnapTime != TimeSpan.Zero) return closeSnapTime; } @@ -263,10 +275,10 @@ namespace Artemis.UI.Shared.Services return time; } - public PropertyInputViewModel CreatePropertyInputViewModel(LayerProperty layerProperty) + public PropertyInputViewModel? CreatePropertyInputViewModel(LayerProperty layerProperty) { - Type viewModelType = null; - PropertyInputRegistration registration = RegisteredPropertyEditors.FirstOrDefault(r => r.SupportedType == typeof(T)); + Type? viewModelType = null; + PropertyInputRegistration? registration = RegisteredPropertyEditors.FirstOrDefault(r => r.SupportedType == typeof(T)); // Check for enums if no supported type was found if (registration == null && typeof(T).IsEnum) @@ -281,22 +293,30 @@ namespace Artemis.UI.Shared.Services else return null; + if (viewModelType == null) + return null; + ConstructorArgument parameter = new ConstructorArgument("layerProperty", layerProperty); - IKernel kernel = registration != null ? registration.Plugin.Kernel : _kernel; + // ReSharper disable once InconsistentlySynchronizedField + // When you've just spent the last 2 hours trying to figure out a deadlock and reach this line, I'm so, so sorry. I thought this would be fine. + IKernel kernel = registration?.Plugin.Kernel ?? _kernel; return (PropertyInputViewModel) kernel.Get(viewModelType, parameter); } - public ProfileModule GetCurrentModule() + public ProfileModule? GetCurrentModule() { return SelectedProfile?.Module; } private void ReloadProfile() { + if (SelectedProfile == null) + return; + // Trigger a profile change OnSelectedProfileChanged(new ProfileEventArgs(SelectedProfile, SelectedProfile)); // Trigger a selected element change - RenderProfileElement previousSelectedProfileElement = SelectedProfileElement; + RenderProfileElement? previousSelectedProfileElement = SelectedProfileElement; if (SelectedProfileElement is Folder folder) SelectedProfileElement = SelectedProfile.GetAllFolders().FirstOrDefault(f => f.EntityId == folder.EntityId); else if (SelectedProfileElement is Layer layer) @@ -305,22 +325,24 @@ namespace Artemis.UI.Shared.Services // Trigger selected data binding change if (SelectedDataBinding != null) { - SelectedDataBinding = SelectedProfileElement?.GetAllLayerProperties()?.FirstOrDefault(p => p.Path == SelectedDataBinding.Path); + SelectedDataBinding = SelectedProfileElement?.GetAllLayerProperties().FirstOrDefault(p => p.Path == SelectedDataBinding.Path); OnSelectedDataBindingChanged(); } UpdateProfilePreview(); } - public event EventHandler ProfileSelected; - public event EventHandler SelectedProfileUpdated; - public event EventHandler ProfileElementSelected; - public event EventHandler SelectedProfileElementUpdated; - public event EventHandler SelectedDataBindingChanged; - public event EventHandler CurrentTimeChanged; - public event EventHandler PixelsPerSecondChanged; - public event EventHandler ProfilePreviewUpdated; - public event EventHandler CurrentTimelineChanged; + #region Events + + public event EventHandler? ProfileSelected; + public event EventHandler? SelectedProfileUpdated; + public event EventHandler? ProfileElementSelected; + public event EventHandler? SelectedProfileElementUpdated; + public event EventHandler? SelectedDataBindingChanged; + public event EventHandler? CurrentTimeChanged; + public event EventHandler? PixelsPerSecondChanged; + public event EventHandler? ProfilePreviewUpdated; + public event EventHandler? CurrentTimelineChanged; protected virtual void OnSelectedProfileChanged(ProfileEventArgs e) { @@ -367,10 +389,13 @@ namespace Artemis.UI.Shared.Services SelectedDataBindingChanged?.Invoke(this, EventArgs.Empty); } - private void SelectedProfileOnDeactivated(object sender, EventArgs e) + private void SelectedProfileOnDeactivated(object? sender, EventArgs e) { // Execute.PostToUIThread(() => ChangeSelectedProfile(null)); ChangeSelectedProfile(null); } + + #endregion + } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Utilities/ShortcutUtilities.cs b/src/Artemis.UI.Shared/Utilities/ShortcutUtilities.cs index 79f45cc8d..dcdfb26ff 100644 --- a/src/Artemis.UI.Shared/Utilities/ShortcutUtilities.cs +++ b/src/Artemis.UI.Shared/Utilities/ShortcutUtilities.cs @@ -9,8 +9,8 @@ namespace Artemis.UI.Shared /// public class ShortcutUtilities { - private static readonly Type m_type = Type.GetTypeFromProgID("WScript.Shell"); - private static readonly object m_shell = Activator.CreateInstance(m_type); + private static readonly Type MType = Type.GetTypeFromProgID("WScript.Shell")!; + private static readonly object MShell = Activator.CreateInstance(MType)!; /// /// Creates a shortcut @@ -24,7 +24,10 @@ namespace Artemis.UI.Shared /// The icon path of the shortcut public static void Create(string fileName, string targetPath, string arguments, string workingDirectory, string description, string hotkey, string iconPath) { - IWshShortcut shortcut = (IWshShortcut) m_type.InvokeMember("CreateShortcut", BindingFlags.InvokeMethod, null, m_shell, new object[] {fileName}); + IWshShortcut? shortcut = (IWshShortcut?) MType.InvokeMember("CreateShortcut", BindingFlags.InvokeMethod, null, MShell, new object[] {fileName}); + if (shortcut == null) + throw new ArtemisSharedUIException("InvokeMember CreateShortcut returned null"); + shortcut.Description = description; shortcut.Hotkey = hotkey; shortcut.TargetPath = targetPath; @@ -34,7 +37,7 @@ namespace Artemis.UI.Shared shortcut.IconLocation = iconPath; shortcut.Save(); } - + [ComImport] [TypeLibType(0x1040)] [Guid("F935DC23-1CF0-11D0-ADB9-00C04FD58A0B")] @@ -118,14 +121,7 @@ namespace Artemis.UI.Shared } [DispId(0x3ee)] - int WindowStyle - { - [DispId(0x3ee)] - get; - [param: In] - [DispId(0x3ee)] - set; - } + int WindowStyle { [DispId(0x3ee)] get; [param: In] [DispId(0x3ee)] set; } [DispId(0x3ef)] string WorkingDirectory @@ -141,6 +137,7 @@ namespace Artemis.UI.Shared [TypeLibFunc(0x40)] [DispId(0x7d0)] + // ReSharper disable once InconsistentNaming - No idea if that breaks it and cba to test void Load([In] [MarshalAs(UnmanagedType.BStr)] string PathLink); [DispId(0x7d1)] diff --git a/src/Artemis.UI.Shared/Utilities/VisualTreeUtilities.cs b/src/Artemis.UI.Shared/Utilities/VisualTreeUtilities.cs index ea069f53a..2664d1c7a 100644 --- a/src/Artemis.UI.Shared/Utilities/VisualTreeUtilities.cs +++ b/src/Artemis.UI.Shared/Utilities/VisualTreeUtilities.cs @@ -20,12 +20,12 @@ namespace Artemis.UI.Shared /// If not matching item can be found, /// a null parent is being returned. /// - public static T FindChild(DependencyObject parent, string childName) where T : DependencyObject + public static T? FindChild(DependencyObject? parent, string? childName) where T : DependencyObject { // Confirm parent and childName are valid. if (parent == null) return null; - T foundChild = null; + T? foundChild = null; int childrenCount = VisualTreeHelper.GetChildrenCount(parent); for (int i = 0; i < childrenCount; i++) @@ -72,10 +72,10 @@ namespace Artemis.UI.Shared /// If not matching item can be found, /// a null parent is being returned. /// - public static T FindParent(DependencyObject child, string parentName) where T : DependencyObject + public static T? FindParent(DependencyObject child, string? parentName) where T : DependencyObject { // Get parent item - DependencyObject parentObject = VisualTreeHelper.GetParent(child); + DependencyObject? parentObject = VisualTreeHelper.GetParent(child); // We've reached the end of the tree if (parentObject == null) diff --git a/src/Artemis.UI/Bootstrapper.cs b/src/Artemis.UI/Bootstrapper.cs index 7f9327d4a..7f9496f63 100644 --- a/src/Artemis.UI/Bootstrapper.cs +++ b/src/Artemis.UI/Bootstrapper.cs @@ -14,7 +14,6 @@ using Artemis.Core.Ninject; using Artemis.Core.Services; using Artemis.UI.Ninject; using Artemis.UI.Screens; -using Artemis.UI.Screens.Splash; using Artemis.UI.Shared; using Artemis.UI.Shared.Services; using Artemis.UI.Stylet; @@ -88,7 +87,8 @@ namespace Artemis.UI protected override void ConfigureIoC(IKernel kernel) { - // kernel.Settings.InjectNonPublic = true; + // This is kinda needed for the VM factories in the Shared UI but perhaps there's a less global solution + kernel.Settings.InjectNonPublic = true; // Load the UI modules kernel.Load(); @@ -123,7 +123,7 @@ namespace Artemis.UI Execute.OnUIThread(() => Application.Current.Shutdown()); } - private void CreateDataDirectory(ILogger logger) + private static void CreateDataDirectory(ILogger logger) { // Ensure the data folder exists if (Directory.Exists(Constants.DataFolder)) @@ -161,14 +161,5 @@ namespace Artemis.UI Environment.Exit(1); }); } - - private void ShowMainWindow(IWindowManager windowManager, SplashViewModel splashViewModel) - { - Execute.OnUIThread(() => - { - windowManager.ShowWindow(RootViewModel); - splashViewModel.RequestClose(); - }); - } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Modules/Tabs/ActivationRequirementViewModel.cs b/src/Artemis.UI/Screens/Modules/Tabs/ActivationRequirementViewModel.cs index ad2f866ee..628f3d4a2 100644 --- a/src/Artemis.UI/Screens/Modules/Tabs/ActivationRequirementViewModel.cs +++ b/src/Artemis.UI/Screens/Modules/Tabs/ActivationRequirementViewModel.cs @@ -1,11 +1,12 @@ -using System.Timers; +using System; +using System.Timers; using Artemis.Core.Modules; using Humanizer; using Stylet; namespace Artemis.UI.Screens.Modules.Tabs { - public class ActivationRequirementViewModel : Screen + public sealed class ActivationRequirementViewModel : Screen, IDisposable { private readonly IModuleActivationRequirement _activationRequirement; private readonly Timer _updateTimer; @@ -60,5 +61,15 @@ namespace Artemis.UI.Screens.Modules.Tabs RequirementDescription = _activationRequirement.GetUserFriendlyDescription(); RequirementMet = _activationRequirement.Evaluate(); } + + #region IDisposable + + /// + public void Dispose() + { + _updateTimer?.Dispose(); + } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Abstract/DataModelConditionPredicateViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Abstract/DataModelConditionPredicateViewModel.cs index 5080adefc..f0a4a4ba5 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Abstract/DataModelConditionPredicateViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Abstract/DataModelConditionPredicateViewModel.cs @@ -5,27 +5,25 @@ using System.Windows; using System.Windows.Media; using Artemis.Core; using Artemis.Core.Services; -using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract; using Artemis.UI.Shared; using Artemis.UI.Shared.Input; using Artemis.UI.Shared.Services; using Stylet; -namespace Artemis.UI.Screens.ProfileEditor.Conditions +namespace Artemis.UI.Screens.ProfileEditor.Conditions.Abstract { public abstract class DataModelConditionPredicateViewModel : DataModelConditionViewModel, IDisposable { private readonly IConditionOperatorService _conditionOperatorService; private readonly IDataModelUIService _dataModelUIService; private readonly IProfileEditorService _profileEditorService; - private BindableCollection _operators; private DataModelStaticViewModel _rightSideInputViewModel; private DataModelDynamicViewModel _rightSideSelectionViewModel; private BaseConditionOperator _selectedOperator; private List _supportedInputTypes; - public DataModelConditionPredicateViewModel( + protected DataModelConditionPredicateViewModel( DataModelConditionPredicate dataModelConditionPredicate, IProfileEditorService profileEditorService, IDataModelUIService dataModelUIService, @@ -199,17 +197,27 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions #region IDisposable + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + if (LeftSideSelectionViewModel != null) + { + LeftSideSelectionViewModel.PropertySelected -= LeftSideOnPropertySelected; + LeftSideSelectionViewModel.Dispose(); + LeftSideSelectionViewModel = null; + } + + DisposeRightSideStaticViewModel(); + DisposeRightSideDynamicViewModel(); + } + } + + /// public void Dispose() { - if (LeftSideSelectionViewModel != null) - { - LeftSideSelectionViewModel.PropertySelected -= LeftSideOnPropertySelected; - LeftSideSelectionViewModel.Dispose(); - LeftSideSelectionViewModel = null; - } - - DisposeRightSideStaticViewModel(); - DisposeRightSideDynamicViewModel(); + Dispose(true); + GC.SuppressFinalize(this); } #endregion @@ -284,7 +292,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions Update(); } - private void RightSideInputViewModelOnSwitchToDynamicRequested(object? sender, EventArgs e) + private void RightSideInputViewModelOnSwitchToDynamicRequested(object sender, EventArgs e) { DataModelConditionPredicate.PredicateType = ProfileRightSideType.Dynamic; Update(); diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionEventViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionEventViewModel.cs index 324dd7e7e..514440a33 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionEventViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionEventViewModel.cs @@ -10,7 +10,7 @@ using Artemis.UI.Shared.Services; namespace Artemis.UI.Screens.ProfileEditor.Conditions { - public class DataModelConditionEventViewModel : DataModelConditionViewModel, IDisposable + public sealed class DataModelConditionEventViewModel : DataModelConditionViewModel, IDisposable { private readonly IDataModelConditionsVmFactory _dataModelConditionsVmFactory; private readonly IDataModelUIService _dataModelUIService; @@ -90,7 +90,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions #region Event handlers - private void LeftSideSelectionViewModelOnPropertySelected(object? sender, DataModelInputDynamicEventArgs e) + private void LeftSideSelectionViewModelOnPropertySelected(object sender, DataModelInputDynamicEventArgs e) { ApplyEvent(); } @@ -104,7 +104,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions LeftSideSelectionViewModel.Dispose(); LeftSideSelectionViewModel.PropertySelected -= LeftSideSelectionViewModelOnPropertySelected; } - + #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListViewModel.cs index c1da5d680..90e4694d0 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListViewModel.cs @@ -11,7 +11,7 @@ using Humanizer; namespace Artemis.UI.Screens.ProfileEditor.Conditions { - public class DataModelConditionListViewModel : DataModelConditionViewModel, IDisposable + public sealed class DataModelConditionListViewModel : DataModelConditionViewModel, IDisposable { private readonly IDataModelConditionsVmFactory _dataModelConditionsVmFactory; private readonly IDataModelUIService _dataModelUIService; @@ -92,7 +92,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions Update(); } - + public override void Update() { LeftSideSelectionViewModel.ChangeDataModelPath(DataModelConditionList.ListPath); @@ -130,15 +130,19 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions base.OnInitialActivate(); } - private void LeftSideSelectionViewModelOnPropertySelected(object? sender, DataModelInputDynamicEventArgs e) + private void LeftSideSelectionViewModelOnPropertySelected(object sender, DataModelInputDynamicEventArgs e) { ApplyList(); } + #region IDisposable + public void Dispose() { LeftSideSelectionViewModel.Dispose(); LeftSideSelectionViewModel.PropertySelected -= LeftSideSelectionViewModelOnPropertySelected; } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionEventPredicateViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionEventPredicateViewModel.cs index 412fef906..71e30559e 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionEventPredicateViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionEventPredicateViewModel.cs @@ -5,6 +5,7 @@ using System.Windows.Media; using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Extensions; +using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract; using Artemis.UI.Shared; using Artemis.UI.Shared.Services; diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateViewModel.cs index 5c7f781c5..0ea8d96ee 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateViewModel.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Artemis.Core; using Artemis.Core.Services; +using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract; using Artemis.UI.Shared; using Artemis.UI.Shared.Services; diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionListPredicateViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionListPredicateViewModel.cs index 6f0c9bdf0..fbc4730b1 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionListPredicateViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionListPredicateViewModel.cs @@ -5,6 +5,7 @@ using System.Windows.Media; using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Extensions; +using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract; using Artemis.UI.Shared; using Artemis.UI.Shared.Services; diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs index c4646dd37..a04d3d563 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs @@ -114,7 +114,7 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions RenderProfileElement.DisplayCondition.ChildRemoved += DisplayConditionOnChildrenModified; } - private void DisplayConditionOnChildrenModified(object? sender, EventArgs e) + private void DisplayConditionOnChildrenModified(object sender, EventArgs e) { DisplayStartHint = !RenderProfileElement.DisplayCondition.Children.Any(); IsEventCondition = RenderProfileElement.DisplayCondition.Children.Any(c => c is DataModelConditionEvent); diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeViewModel.cs index ee17779a0..620bdaee2 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeViewModel.cs @@ -10,7 +10,7 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.ConditionalDataBinding { - public class ConditionalDataBindingModeViewModel : Conductor>.Collection.AllActive, IDataBindingModeViewModel + public sealed class ConditionalDataBindingModeViewModel : Conductor>.Collection.AllActive, IDataBindingModeViewModel { private readonly IDataBindingsVmFactory _dataBindingsVmFactory; private readonly IProfileEditorService _profileEditorService; @@ -109,7 +109,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.Conditio } #region IDisposable - + + /// public void Dispose() { ConditionalDataBinding.ConditionsUpdated -= ConditionalDataBindingOnConditionsUpdated; diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionViewModel.cs index 9b34f64ac..e5980c115 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionViewModel.cs @@ -10,11 +10,11 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.ConditionalDataBinding { - public class DataBindingConditionViewModel : Conductor, IDisposable + public sealed class DataBindingConditionViewModel : Conductor, IDisposable { - private readonly IProfileEditorService _profileEditorService; private readonly IDataModelConditionsVmFactory _dataModelConditionsVmFactory; private readonly IDataModelUIService _dataModelUIService; + private readonly IProfileEditorService _profileEditorService; public DataBindingConditionViewModel(DataBindingCondition dataBindingCondition, IProfileEditorService profileEditorService, @@ -44,11 +44,6 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.Conditio ValueViewModel.Value = DataBindingCondition.Value; } - public void Dispose() - { - ValueViewModel.Dispose(); - } - private void ActiveItemOnUpdated(object sender, EventArgs e) { if (!ActiveItem.GetChildren().Any()) @@ -60,5 +55,15 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.Conditio DataBindingCondition.Value = (TProperty) Convert.ChangeType(e.Value, typeof(TProperty)); _profileEditorService.UpdateSelectedProfileElement(); } + + #region IDisposable + + /// + public void Dispose() + { + ValueViewModel?.Dispose(); + } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs index 95d873b28..76f1b24ce 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs @@ -10,17 +10,17 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings { - public class DataBindingViewModel : Conductor, IDataBindingViewModel + public sealed class DataBindingViewModel : Conductor, IDataBindingViewModel { private readonly IDataBindingsVmFactory _dataBindingsVmFactory; private readonly IProfileEditorService _profileEditorService; private readonly Timer _updateTimer; + private bool _applyTestResultToLayer; private int _easingTime; private bool _isDataBindingEnabled; private bool _isEasingTimeEnabled; private DataBindingModeType _selectedDataBindingMode; private TimelineEasingViewModel _selectedEasingViewModel; - private bool _applyTestResultToLayer; private bool _updating; private bool _updatingTestResult; @@ -46,12 +46,6 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings TestResultValue = dataModelUIService.GetDataModelDisplayViewModel(typeof(TProperty), null, true); } - protected override void OnInitialActivate() - { - base.OnInitialActivate(); - Initialize(); - } - public DataBindingRegistration Registration { get; } public BindableCollection DataBindingModes { get; } @@ -115,12 +109,10 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings } } - public void Dispose() + protected override void OnInitialActivate() { - _updateTimer.Dispose(); - _updateTimer.Elapsed -= OnUpdateTimerOnElapsed; - - Registration.LayerProperty.Updated -= LayerPropertyOnUpdated; + base.OnInitialActivate(); + Initialize(); } private void Initialize() @@ -171,18 +163,12 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings EasingTime = (int) Registration.DataBinding.EasingTime.TotalMilliseconds; SelectedEasingViewModel = EasingViewModels.First(vm => vm.EasingFunction == Registration.DataBinding.EasingFunction); IsEasingTimeEnabled = EasingTime > 0; - switch (Registration.DataBinding.DataBindingMode) + SelectedDataBindingMode = Registration.DataBinding.DataBindingMode switch { - case DirectDataBinding _: - SelectedDataBindingMode = DataBindingModeType.Direct; - break; - case ConditionalDataBinding _: - SelectedDataBindingMode = DataBindingModeType.Conditional; - break; - default: - SelectedDataBindingMode = DataBindingModeType.None; - break; - } + DirectDataBinding _ => DataBindingModeType.Direct, + ConditionalDataBinding _ => DataBindingModeType.Conditional, + _ => DataBindingModeType.None + }; ActiveItem?.Update(); @@ -250,7 +236,9 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings TestResultValue.UpdateValue(Registration.DataBinding != null ? Registration.DataBinding.GetValue(currentValue) : default); } else + { TestResultValue.UpdateValue(Registration.DataBinding != null ? Registration.DataBinding.GetValue(default) : default); + } if (ApplyTestResultToLayer) { @@ -288,11 +276,24 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings UpdateTestResult(); } - private void LayerPropertyOnUpdated(object? sender, LayerPropertyEventArgs e) + private void LayerPropertyOnUpdated(object sender, LayerPropertyEventArgs e) { - if (ApplyTestResultToLayer) + if (ApplyTestResultToLayer) Registration.DataBinding?.Apply(); } + + #region IDisposable + + /// + public void Dispose() + { + _updateTimer.Dispose(); + _updateTimer.Elapsed -= OnUpdateTimerOnElapsed; + + Registration.LayerProperty.Updated -= LayerPropertyOnUpdated; + } + + #endregion } public interface IDataBindingViewModel : IScreen, IDisposable diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs index bfe1f5a54..b772e27e4 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs @@ -52,7 +52,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings SelectedItemIndex = 0; } - private void ProfileEditorServiceOnSelectedDataBindingChanged(object? sender, EventArgs e) + private void ProfileEditorServiceOnSelectedDataBindingChanged(object sender, EventArgs e) { CreateDataBindingViewModels(); } diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DataBindingModifierViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DataBindingModifierViewModel.cs index 65620f0e5..839619fa1 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DataBindingModifierViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DataBindingModifierViewModel.cs @@ -12,7 +12,7 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDataBinding { - public class DataBindingModifierViewModel : PropertyChangedBase, IDisposable + public sealed class DataBindingModifierViewModel : PropertyChangedBase, IDisposable { private readonly IDataBindingService _dataBindingService; private readonly IDataModelUIService _dataModelUIService; @@ -161,6 +161,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDa #region IDisposable + /// public void Dispose() { DisposeDynamicSelectionViewModel(); @@ -193,13 +194,13 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDa #region Event handlers - private void DynamicSelectionViewModelOnSwitchToStaticRequested(object? sender, EventArgs e) + private void DynamicSelectionViewModelOnSwitchToStaticRequested(object sender, EventArgs e) { Modifier.ParameterType = ProfileRightSideType.Static; Update(); } - private void StaticInputViewModelOnSwitchToDynamicRequested(object? sender, EventArgs e) + private void StaticInputViewModelOnSwitchToDynamicRequested(object sender, EventArgs e) { Modifier.ParameterType = ProfileRightSideType.Dynamic; Update(); diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeViewModel.cs index cfaa109b3..d7c9272b6 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeViewModel.cs @@ -12,7 +12,7 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDataBinding { - public class DirectDataBindingModeViewModel : Screen, IDataBindingModeViewModel + public sealed class DirectDataBindingModeViewModel : Screen, IDataBindingModeViewModel { private readonly IDataBindingsVmFactory _dataBindingsVmFactory; private readonly IDataModelUIService _dataModelUIService; @@ -70,6 +70,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDa #region IDisposable + /// public void Dispose() { TargetSelectionViewModel.PropertySelected -= TargetSelectionViewModelOnPropertySelected; diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs index 9932d6757..d2476c19f 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs @@ -9,7 +9,7 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties { - public class LayerPropertyGroupViewModel : PropertyChangedBase, IDisposable + public sealed class LayerPropertyGroupViewModel : PropertyChangedBase, IDisposable { private readonly ILayerPropertyVmFactory _layerPropertyVmFactory; private bool _isVisible; @@ -40,9 +40,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties get => _isVisible; set => SetAndNotify(ref _isVisible, value); } - - public bool IsHighlighted => false; - + public bool IsExpanded { get => LayerPropertyGroup.ProfileElement.IsPropertyGroupExpanded(LayerPropertyGroup); @@ -53,6 +51,9 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties } } + #region IDisposable + + /// public void Dispose() { TimelineGroupViewModel.Dispose(); @@ -64,6 +65,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties } } + #endregion + public void UpdateOrder(int order) { LayerPropertyGroup.LayerEffect.Order = order; @@ -93,7 +96,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties /// /// The position at which to start removing keyframes, if null this will start at the first keyframe /// The position at which to start removing keyframes, if null this will end at the last keyframe - public virtual void WipeKeyframes(TimeSpan? start, TimeSpan? end) + public void WipeKeyframes(TimeSpan? start, TimeSpan? end) { foreach (PropertyChangedBase child in Children) { @@ -139,7 +142,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties { PropertyDescriptionAttribute propertyAttribute = (PropertyDescriptionAttribute) Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyDescriptionAttribute)); PropertyGroupDescriptionAttribute groupAttribute = (PropertyGroupDescriptionAttribute) Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyGroupDescriptionAttribute)); - object? value = propertyInfo.GetValue(LayerPropertyGroup); + object value = propertyInfo.GetValue(LayerPropertyGroup); // Create VMs for properties on the group if (propertyAttribute != null && value is ILayerProperty layerProperty) diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs index ce5c39ed7..b72463006 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs @@ -7,7 +7,7 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties { - public class LayerPropertyViewModel : PropertyChangedBase, IDisposable + public sealed class LayerPropertyViewModel : PropertyChangedBase, IDisposable { private bool _isVisible; private bool _isHighlighted; diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineGroupViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineGroupViewModel.cs index ea75c170b..8463787bc 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineGroupViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineGroupViewModel.cs @@ -7,7 +7,7 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline { - public class TimelineGroupViewModel : PropertyChangedBase, IDisposable + public sealed class TimelineGroupViewModel : PropertyChangedBase, IDisposable { private readonly IProfileEditorService _profileEditorService; diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs index 6c9b8e7d5..6e06a734a 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs @@ -7,11 +7,10 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline { - public class TimelineKeyframeViewModel : Screen, ITimelineKeyframeViewModel + public sealed class TimelineKeyframeViewModel : Screen, ITimelineKeyframeViewModel { private readonly IProfileEditorService _profileEditorService; - private BindableCollection _easingViewModels; private bool _isSelected; private string _timestamp; private double _x; @@ -65,7 +64,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline Timestamp = $"{Math.Floor(LayerPropertyKeyframe.Position.TotalSeconds):00}.{LayerPropertyKeyframe.Position.Milliseconds:000}"; } - private void ProfileEditorServiceOnPixelsPerSecondChanged(object? sender, EventArgs e) + private void ProfileEditorServiceOnPixelsPerSecondChanged(object sender, EventArgs e) { Update(); } diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs index 066b66cdb..de21e2e59 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs @@ -7,7 +7,7 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline { - public class TimelinePropertyViewModel : Conductor>.Collection.AllActive, ITimelinePropertyViewModel + public sealed class TimelinePropertyViewModel : Conductor>.Collection.AllActive, ITimelinePropertyViewModel { private readonly IProfileEditorService _profileEditorService; private double _width; diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs index 974a64b66..75a795339 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs @@ -7,7 +7,6 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Input; using Artemis.Core; -using Artemis.UI.Screens.ProfileEditor.Dialogs; using Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline.Dialogs; using Artemis.UI.Shared; using Artemis.UI.Shared.Services; @@ -15,19 +14,19 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline { - public class TimelineSegmentViewModel : Screen, IDisposable + public sealed class TimelineSegmentViewModel : Screen, IDisposable { private readonly IDialogService _dialogService; private bool _draggingSegment; + private bool _segmentEnabled; + private TimeSpan _segmentEnd; + private TimeSpan _segmentLength; + private TimeSpan _segmentStart; + private double _segmentStartPosition; + private double _segmentWidth; private bool _showDisableButton; private bool _showRepeatButton; private bool _showSegmentName; - private TimeSpan _segmentLength; - private TimeSpan _segmentStart; - private TimeSpan _segmentEnd; - private double _segmentWidth; - private bool _segmentEnabled; - private double _segmentStartPosition; public TimelineSegmentViewModel(SegmentViewModelType segment, BindableCollection layerPropertyGroups, IProfileEditorService profileEditorService, IDialogService dialogService) @@ -139,6 +138,40 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline await _dialogService.ShowDialog(new Dictionary {{"segment", this}}); } + public void ShiftNextSegment(TimeSpan amount) + { + if (Segment == SegmentViewModelType.Start) + ShiftKeyframes(SelectedProfileElement.Timeline.StartSegmentEndPosition, null, amount); + else if (Segment == SegmentViewModelType.Main) + ShiftKeyframes(SelectedProfileElement.Timeline.MainSegmentEndPosition, null, amount); + else if (Segment == SegmentViewModelType.End) + ShiftKeyframes(SelectedProfileElement.Timeline.EndSegmentEndPosition, null, amount); + } + + private void WipeKeyframes(TimeSpan? start, TimeSpan? end) + { + foreach (LayerPropertyGroupViewModel layerPropertyGroupViewModel in LayerPropertyGroups) + layerPropertyGroupViewModel.WipeKeyframes(start, end); + } + + private void ShiftKeyframes(TimeSpan? start, TimeSpan? end, TimeSpan amount) + { + foreach (LayerPropertyGroupViewModel layerPropertyGroupViewModel in LayerPropertyGroups) + layerPropertyGroupViewModel.ShiftKeyframes(start, end, amount); + } + + #region IDIsposable + + public void Dispose() + { + ProfileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged; + ProfileEditorService.ProfileElementSelected -= ProfileEditorServiceOnProfileElementSelected; + if (SelectedProfileElement != null) + SelectedProfileElement.Timeline.PropertyChanged -= TimelineOnPropertyChanged; + } + + #endregion + #region Updating private void UpdateHeader() @@ -283,7 +316,9 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline } // If holding down control, round to the closest 50ms else if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) + { newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 50.0) * 50.0); + } UpdateLength(newTime); } @@ -304,7 +339,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline SelectedProfileElement.Timeline.MainSegmentEndPosition = newTime; else if (Segment == SegmentViewModelType.End) SelectedProfileElement.Timeline.EndSegmentEndPosition = newTime; - + if (difference < TimeSpan.Zero) ShiftNextSegment(difference); @@ -313,21 +348,9 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline #endregion - #region IDIsposable - - public void Dispose() - { - ProfileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged; - ProfileEditorService.ProfileElementSelected -= ProfileEditorServiceOnProfileElementSelected; - if (SelectedProfileElement != null) - SelectedProfileElement.Timeline.PropertyChanged -= TimelineOnPropertyChanged; - } - - #endregion - #region Event handlers - private void ProfileEditorServiceOnProfileElementSelected(object? sender, RenderProfileElementEventArgs e) + private void ProfileEditorServiceOnProfileElementSelected(object sender, RenderProfileElementEventArgs e) { if (e.PreviousRenderProfileElement != null) e.PreviousRenderProfileElement.Timeline.PropertyChanged -= TimelineOnPropertyChanged; @@ -347,34 +370,12 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline NotifyOfPropertyChange(nameof(RepeatSegment)); } - private void ProfileEditorServiceOnPixelsPerSecondChanged(object? sender, EventArgs e) + private void ProfileEditorServiceOnPixelsPerSecondChanged(object sender, EventArgs e) { Update(); } #endregion - - public void ShiftNextSegment(TimeSpan amount) - { - if (Segment == SegmentViewModelType.Start) - ShiftKeyframes(SelectedProfileElement.Timeline.StartSegmentEndPosition, null, amount); - else if (Segment == SegmentViewModelType.Main) - ShiftKeyframes(SelectedProfileElement.Timeline.MainSegmentEndPosition, null, amount); - else if (Segment == SegmentViewModelType.End) - ShiftKeyframes(SelectedProfileElement.Timeline.EndSegmentEndPosition, null, amount); - } - - private void WipeKeyframes(TimeSpan? start, TimeSpan? end) - { - foreach (LayerPropertyGroupViewModel layerPropertyGroupViewModel in LayerPropertyGroups) - layerPropertyGroupViewModel.WipeKeyframes(start, end); - } - - private void ShiftKeyframes(TimeSpan? start, TimeSpan? end, TimeSpan amount) - { - foreach (LayerPropertyGroupViewModel layerPropertyGroupViewModel in LayerPropertyGroups) - layerPropertyGroupViewModel.ShiftKeyframes(start, end, amount); - } } public enum SegmentViewModelType diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs index 5dd9a62b9..c25a18230 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs @@ -6,14 +6,13 @@ using System.Windows; using System.Windows.Input; using System.Windows.Media; using System.Windows.Shapes; -using Artemis.Core; using Artemis.UI.Shared; using Artemis.UI.Shared.Services; using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline { - public class TimelineViewModel : Screen, IDisposable + public sealed class TimelineViewModel : Screen, IDisposable { private readonly IProfileEditorService _profileEditorService; private RectangleGeometry _selectionRectangle; @@ -88,7 +87,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline Update(); } - private void ProfileEditorServiceOnProfileElementSelected(object? sender, RenderProfileElementEventArgs e) + private void ProfileEditorServiceOnProfileElementSelected(object sender, RenderProfileElementEventArgs e) { if (e.PreviousRenderProfileElement != null) e.PreviousRenderProfileElement.Timeline.PropertyChanged -= TimelineOnPropertyChanged; @@ -98,6 +97,18 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline Update(); } + #region IDisposable + + public void Dispose() + { + _profileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged; + _profileEditorService.ProfileElementSelected -= ProfileEditorServiceOnProfileElementSelected; + if (_profileEditorService.SelectedProfileElement != null) + _profileEditorService.SelectedProfileElement.Timeline.PropertyChanged -= TimelineOnPropertyChanged; + } + + #endregion + #region Command handlers public void KeyframeMouseDown(object sender, MouseButtonEventArgs e) @@ -105,8 +116,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline // if (e.LeftButton == MouseButtonState.Released) // return; - ITimelineKeyframeViewModel viewModel = (sender as Ellipse)?.DataContext as ITimelineKeyframeViewModel; - if (viewModel == null) + if (!((sender as Ellipse)?.DataContext is ITimelineKeyframeViewModel viewModel)) return; ((IInputElement) sender).CaptureMouse(); @@ -206,7 +216,6 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline keyframeViewModel.SaveOffsetToKeyframe(sourceKeyframeViewModel); if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) - { cursorTime = _profileEditorService.SnapToTimeline( cursorTime, TimeSpan.FromMilliseconds(1000f / _profileEditorService.PixelsPerSecond * 5), @@ -214,7 +223,6 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline false, keyframeViewModels.Where(k => k != sourceKeyframeViewModel).Select(k => k.Position).ToList() ); - } sourceKeyframeViewModel.UpdatePosition(cursorTime); @@ -306,15 +314,11 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline int clickedIndex = keyframeViewModels.IndexOf(clicked); if (clickedIndex < selectedIndex) - { foreach (ITimelineKeyframeViewModel keyframeViewModel in keyframeViewModels.Skip(clickedIndex).Take(selectedIndex - clickedIndex + 1)) keyframeViewModel.IsSelected = true; - } else - { foreach (ITimelineKeyframeViewModel keyframeViewModel in keyframeViewModels.Skip(selectedIndex).Take(clickedIndex - selectedIndex + 1)) keyframeViewModel.IsSelected = true; - } } else if (toggle) { @@ -340,17 +344,5 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline } #endregion - - #region IDisposable - - public void Dispose() - { - _profileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged; - _profileEditorService.ProfileElementSelected -= ProfileEditorServiceOnProfileElementSelected; - if (_profileEditorService.SelectedProfileElement != null) - _profileEditorService.SelectedProfileElement.Timeline.PropertyChanged -= TimelineOnPropertyChanged; - } - - #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs index deb82fe90..89f0b0370 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs @@ -1,5 +1,4 @@ using System; -using System.ComponentModel; using System.Linq; using Artemis.Core; using Artemis.UI.Shared; @@ -8,7 +7,7 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree { - public class TreePropertyViewModel : Screen, ITreePropertyViewModel + public sealed class TreePropertyViewModel : Screen, ITreePropertyViewModel { private readonly IProfileEditorService _profileEditorService; private PropertyInputViewModel _propertyInputViewModel; @@ -26,7 +25,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree LayerProperty.DataBindingDisabled += LayerPropertyOnDataBindingChange; LayerPropertyViewModel.IsVisible = !LayerProperty.IsHidden; } - + public LayerProperty LayerProperty { get; } public LayerPropertyViewModel LayerPropertyViewModel { get; } @@ -36,22 +35,6 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree set => SetAndNotify(ref _propertyInputViewModel, value); } - public bool HasDataBinding => LayerProperty.HasDataBinding; - public double GetDepth() - { - int depth = 0; - LayerPropertyGroup current = LayerProperty.LayerPropertyGroup; - while (current != null) - { - depth++; - current = current.Parent; - } - - return depth; - } - - public bool HasPropertyInputViewModel => PropertyInputViewModel != null; - public bool KeyframesEnabled { get => LayerProperty.KeyframesEnabled; @@ -66,6 +49,42 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree _profileEditorService.ChangeSelectedDataBinding(LayerProperty); } + private void ApplyKeyframesEnabled(bool enable) + { + // If enabling keyframes for the first time, add a keyframe with the current value at the current position + if (enable && !LayerProperty.Keyframes.Any()) + LayerProperty.AddKeyframe(new LayerPropertyKeyframe( + LayerProperty.CurrentValue, + _profileEditorService.CurrentTime, + Easings.Functions.Linear, + LayerProperty + )); + // If disabling keyframes, set the base value to the current value + else if (!enable && LayerProperty.Keyframes.Any()) + LayerProperty.BaseValue = LayerProperty.CurrentValue; + + LayerProperty.KeyframesEnabled = enable; + + _profileEditorService.UpdateSelectedProfileElement(); + } + + public bool HasDataBinding => LayerProperty.HasDataBinding; + + public double GetDepth() + { + int depth = 0; + LayerPropertyGroup current = LayerProperty.LayerPropertyGroup; + while (current != null) + { + depth++; + current = current.Parent; + } + + return depth; + } + + public bool HasPropertyInputViewModel => PropertyInputViewModel != null; + #region IDisposable public void Dispose() @@ -79,27 +98,6 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree #endregion - private void ApplyKeyframesEnabled(bool enable) - { - // If enabling keyframes for the first time, add a keyframe with the current value at the current position - if (enable && !LayerProperty.Keyframes.Any()) - { - LayerProperty.AddKeyframe(new LayerPropertyKeyframe( - LayerProperty.CurrentValue, - _profileEditorService.CurrentTime, - Easings.Functions.Linear, - LayerProperty - )); - } - // If disabling keyframes, set the base value to the current value - else if (!enable && LayerProperty.Keyframes.Any()) - LayerProperty.BaseValue = LayerProperty.CurrentValue; - - LayerProperty.KeyframesEnabled = enable; - - _profileEditorService.UpdateSelectedProfileElement(); - } - #region Event handlers private void ProfileEditorServiceOnSelectedDataBindingChanged(object sender, EventArgs e) @@ -107,12 +105,12 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree LayerPropertyViewModel.IsHighlighted = _profileEditorService.SelectedDataBinding == LayerProperty; } - private void LayerPropertyOnVisibilityChanged(object? sender, LayerPropertyEventArgs e) + private void LayerPropertyOnVisibilityChanged(object sender, LayerPropertyEventArgs e) { LayerPropertyViewModel.IsVisible = !LayerProperty.IsHidden; } - private void LayerPropertyOnDataBindingChange(object? sender, LayerPropertyEventArgs e) + private void LayerPropertyOnDataBindingChange(object sender, LayerPropertyEventArgs e) { NotifyOfPropertyChange(nameof(HasDataBinding)); } diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerViewModel.cs index cfa32b93e..7a69797ec 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerViewModel.cs @@ -21,12 +21,11 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem _profileEditorService = profileEditorService; } - public async void CopyElement() + public void CopyElement() { Layer layer = Layer.CreateCopy(); _profileEditorService.UpdateSelectedProfile(); - // await Task.Delay(200); _profileEditorService.ChangeSelectedProfileElement(layer); } diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs index 84cecbe66..a84d409e0 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs @@ -16,9 +16,9 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem public abstract class TreeItemViewModel : Conductor.Collection.AllActive, IDisposable { private readonly IDialogService _dialogService; + private readonly ILayerBrushService _layerBrushService; private readonly IProfileEditorService _profileEditorService; private readonly IProfileTreeVmFactory _profileTreeVmFactory; - private readonly ILayerBrushService _layerBrushService; private readonly ISurfaceService _surfaceService; private ProfileElement _profileElement; @@ -49,11 +49,6 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem public abstract bool SupportsChildren { get; } - public void Dispose() - { - Unsubscribe(); - } - public List GetAllChildren() { List children = new List(); @@ -142,7 +137,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem Layer layer = new Layer(ProfileElement, "New layer"); // Could be null if the default brush got disabled - LayerBrushDescriptor? brush = _layerBrushService.GetDefaultLayerBrush(); + LayerBrushDescriptor brush = _layerBrushService.GetDefaultLayerBrush(); if (brush != null) layer.ChangeLayerBrush(brush); @@ -212,7 +207,6 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem List newChildren = new List(); foreach (ProfileElement profileElement in ProfileElement.Children.OrderBy(c => c.Order)) - { if (profileElement is Folder folder) { if (Items.FirstOrDefault(p => p is FolderViewModel vm && vm.ProfileElement == folder) == null) @@ -223,7 +217,6 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem if (Items.FirstOrDefault(p => p is LayerViewModel vm && vm.ProfileElement == layer) == null) newChildren.Add(_profileTreeVmFactory.LayerViewModel(layer)); } - } if (!newChildren.Any()) return; @@ -262,5 +255,21 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem { UpdateProfileElements(); } + + #region IDisposable + + protected virtual void Dispose(bool disposing) + { + if (disposing) Unsubscribe(); + } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs index 4470da58c..780aac675 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs @@ -391,7 +391,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization UpdateCanSelectEditTool(); } - private void TransformValueChanged(object? sender, LayerPropertyEventArgs e) + private void TransformValueChanged(object sender, LayerPropertyEventArgs e) { if (ActiveToolIndex != 1) ActivateToolByIndex(1); diff --git a/src/Artemis.UI/Screens/ProfileEditor/Visualization/Tools/EditToolViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Visualization/Tools/EditToolViewModel.cs index 8f88fc89b..38db11c66 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Visualization/Tools/EditToolViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Visualization/Tools/EditToolViewModel.cs @@ -192,7 +192,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization.Tools height = layer.Transform.Scale.CurrentValue.Height; break; default: - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(e)); } // Make the sides even if shift is held down @@ -326,10 +326,14 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization.Tools { // Keep the X position static if dragging vertically if (_draggingVertically) + { position.X = _dragStart.X; + } // Keep the Y position static if dragging horizontally else if (_draggingHorizontally) + { position.Y = _dragStart.Y; + } // Snap into place only if the mouse moved atleast a full pixel else if (Math.Abs(position.X - _dragStart.X) > 1 || Math.Abs(position.Y - _dragStart.Y) > 1) { @@ -386,23 +390,23 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization.Tools #region Private methods - private SKPoint RoundPoint(SKPoint point, int decimals) + private static SKPoint RoundPoint(SKPoint point, int decimals) { return new SKPoint((float) Math.Round(point.X, decimals, MidpointRounding.AwayFromZero), (float) Math.Round(point.Y, decimals, MidpointRounding.AwayFromZero)); } - private SKPoint[] UnTransformPoints(SKPoint[] skPoints, Layer layer, SKPoint pivot, bool includeScale) + private static SKPoint[] UnTransformPoints(SKPoint[] skPoints, Layer layer, SKPoint pivot, bool includeScale) { using SKPath counterRotatePath = new SKPath(); counterRotatePath.AddPoly(skPoints, false); - counterRotatePath.Transform(SKMatrix.MakeRotationDegrees(layer.Transform.Rotation.CurrentValue * -1, pivot.X, pivot.Y)); + counterRotatePath.Transform(SKMatrix.CreateRotationDegrees(layer.Transform.Rotation.CurrentValue * -1, pivot.X, pivot.Y)); if (includeScale) - counterRotatePath.Transform(SKMatrix.MakeScale(1f / (layer.Transform.Scale.CurrentValue.Width / 100f), 1f / (layer.Transform.Scale.CurrentValue.Height / 100f))); + counterRotatePath.Transform(SKMatrix.CreateScale(1f / (layer.Transform.Scale.CurrentValue.Width / 100f), 1f / (layer.Transform.Scale.CurrentValue.Height / 100f))); return counterRotatePath.Points; } - private Point GetRelativePosition(object sender, MouseEventArgs mouseEventArgs) + private static Point GetRelativePosition(object sender, MouseEventArgs mouseEventArgs) { DependencyObject parent = VisualTreeHelper.GetParent((DependencyObject) sender); return mouseEventArgs.GetPosition((IInputElement) parent); @@ -410,7 +414,6 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization.Tools private float CalculateAngle(Layer layer, object mouseEventSender, MouseEventArgs mouseEvent) { - Rect layerBounds = _layerEditorService.GetLayerBounds(layer); Point start = _layerEditorService.GetLayerAnchorPosition(layer); Point arrival = GetRelativePosition(mouseEventSender, mouseEvent); diff --git a/src/Artemis.UI/Screens/RootViewModel.cs b/src/Artemis.UI/Screens/RootViewModel.cs index 1b84c0ec7..55e0c719d 100644 --- a/src/Artemis.UI/Screens/RootViewModel.cs +++ b/src/Artemis.UI/Screens/RootViewModel.cs @@ -20,22 +20,22 @@ using Stylet; namespace Artemis.UI.Screens { - public class RootViewModel : Conductor + public sealed class RootViewModel : Conductor, IDisposable { private readonly IRegistrationService _builtInRegistrationService; private readonly PluginSetting _colorScheme; private readonly ICoreService _coreService; private readonly IDebugService _debugService; private readonly IEventAggregator _eventAggregator; + private readonly Timer _frameTimeUpdateTimer; private readonly ISnackbarMessageQueue _snackbarMessageQueue; private readonly ThemeWatcher _themeWatcher; - private readonly Timer _frameTimeUpdateTimer; private readonly PluginSetting _windowSize; private bool _activeItemReady; + private string _frameTime; private bool _lostFocus; private ISnackbarMessageQueue _mainMessageQueue; private string _windowTitle; - private string _frameTime; public RootViewModel( IEventAggregator eventAggregator, @@ -64,7 +64,7 @@ namespace Artemis.UI.Screens ActiveItem = SidebarViewModel.SelectedItem; ActiveItemReady = true; - AssemblyInformationalVersionAttribute? versionAttribute = typeof(RootViewModel).Assembly.GetCustomAttribute(); + AssemblyInformationalVersionAttribute versionAttribute = typeof(RootViewModel).Assembly.GetCustomAttribute(); WindowTitle = $"Artemis {versionAttribute?.InformationalVersion}"; } @@ -141,19 +141,18 @@ namespace Artemis.UI.Screens private void UpdateFrameTime() { - FrameTime = $"Frame time: {_coreService.FrameTime.TotalMilliseconds:F2} ms"; } - + private void SidebarViewModelOnPropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(SidebarViewModel.SelectedItem) && ActiveItem != SidebarViewModel.SelectedItem) { SidebarViewModel.IsSidebarOpen = false; // Don't do a fade when selecting a module because the editor is so bulky the animation slows things down - if (!(SidebarViewModel.SelectedItem is ModuleRootViewModel)) + if (!(SidebarViewModel.SelectedItem is ModuleRootViewModel)) ActiveItemReady = false; - + // Allow the menu to close, it's slower but feels more responsive, funny how that works right Execute.PostToUIThreadAsync(async () => { @@ -209,13 +208,25 @@ namespace Artemis.UI.Screens ApplyColorSchemeSetting(); } + #region IDisposable + + /// + public void Dispose() + { + _frameTimeUpdateTimer?.Dispose(); + } + + #endregion + #region Overrides of Screen protected override void OnViewLoaded() { MaterialWindow window = (MaterialWindow) View; if (_windowSize.Value != null) + { _windowSize.Value.ApplyToWindow(window); + } else { _windowSize.Value = new WindowSize(); @@ -269,7 +280,7 @@ namespace Artemis.UI.Screens protected override void OnClose() { SidebarViewModel.Dispose(); - + // Lets force the GC to run after closing the window so it is obvious to users watching task manager // that closing the UI will decrease the memory footprint of the application. Task.Run(async () => diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs index c818e3cfd..442b0996d 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System; using System.Linq; using System.Timers; using Artemis.Core; @@ -10,14 +10,14 @@ using Stylet; namespace Artemis.UI.Screens.Settings.Debug.Tabs { - public class DataModelDebugViewModel : Screen + public sealed class DataModelDebugViewModel : Screen, IDisposable { private readonly IDataModelUIService _dataModelUIService; private readonly IPluginManagementService _pluginManagementService; private readonly Timer _updateTimer; + private bool _isModuleFilterEnabled; private DataModelPropertiesViewModel _mainDataModel; - private List _modules; private string _propertySearch; private Module _selectedModule; @@ -69,6 +69,17 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs } } + #region Overrides of Screen + + /// + protected override void OnClose() + { + _updateTimer.Dispose(); + base.OnClose(); + } + + #endregion + protected override void OnActivate() { GetDataModel(); @@ -126,5 +137,15 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs else if (!SelectedModule.IsEnabled) SelectedModule = null; } + + #region IDisposable + + /// + public void Dispose() + { + _updateTimer?.Dispose(); + } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsTabViewModel.cs index 84d6be584..080f86424 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsTabViewModel.cs @@ -11,7 +11,6 @@ namespace Artemis.UI.Screens.Settings.Tabs.Devices { private readonly ISettingsVmFactory _settingsVmFactory; private readonly ISurfaceService _surfaceService; - private BindableCollection _deviceSettingsViewModels; public DeviceSettingsTabViewModel(ISurfaceService surfaceService, ISettingsVmFactory settingsVmFactory) { diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs index 11313831d..46a7d04e3 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs @@ -125,34 +125,27 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins private PackIconKind GetIconKind() { - switch (Feature) + return Feature switch { - case BaseDataModelExpansion _: - return PackIconKind.TableAdd; - case DeviceProvider _: - return PackIconKind.Devices; - case ProfileModule _: - return PackIconKind.VectorRectangle; - case Module _: - return PackIconKind.GearBox; - case LayerBrushProvider _: - return PackIconKind.Brush; - case LayerEffectProvider _: - return PackIconKind.AutoAwesome; - } - - return PackIconKind.Plugin; + BaseDataModelExpansion => PackIconKind.TableAdd, + DeviceProvider => PackIconKind.Devices, + ProfileModule => PackIconKind.VectorRectangle, + Module => PackIconKind.GearBox, + LayerBrushProvider => PackIconKind.Brush, + LayerEffectProvider => PackIconKind.AutoAwesome, + _ => PackIconKind.Plugin + }; } #region Event handlers - private void OnFeatureEnabling(object? sender, PluginFeatureEventArgs e) + private void OnFeatureEnabling(object sender, PluginFeatureEventArgs e) { if (e.PluginFeature != Feature) return; Enabling = true; } - private void OnFeatureEnableStopped(object? sender, PluginFeatureEventArgs e) + private void OnFeatureEnableStopped(object sender, PluginFeatureEventArgs e) { if (e.PluginFeature != Feature) return; Enabling = false; diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs index 8029aa330..3d75bc9f6 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs @@ -146,14 +146,14 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins NotifyOfPropertyChange(nameof(CanOpenSettings)); } - private void PluginOnFeatureRemoved(object? sender, PluginFeatureEventArgs e) + private void PluginOnFeatureRemoved(object sender, PluginFeatureEventArgs e) { PluginFeatureViewModel viewModel = Items.FirstOrDefault(i => i.Feature == e.PluginFeature); if (viewModel != null) Items.Remove(viewModel); } - private void PluginOnFeatureAdded(object? sender, PluginFeatureEventArgs e) + private void PluginOnFeatureAdded(object sender, PluginFeatureEventArgs e) { Items.Add(_settingsVmFactory.CreatePluginFeatureViewModel(e.PluginFeature)); } diff --git a/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs b/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs index b7fed07a5..de18768cb 100644 --- a/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs +++ b/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs @@ -22,7 +22,7 @@ using Stylet; namespace Artemis.UI.Screens.Sidebar { - public class SidebarViewModel : PropertyChangedBase, IHandle, IDisposable + public sealed class SidebarViewModel : PropertyChangedBase, IHandle, IDisposable { private readonly Timer _activeModulesUpdateTimer; private readonly IKernel _kernel; @@ -196,16 +196,9 @@ namespace Artemis.UI.Screens.Sidebar #region IDisposable - protected virtual void Dispose(bool disposing) - { - if (disposing) - _activeModulesUpdateTimer?.Dispose(); - } - public void Dispose() { - Dispose(true); - GC.SuppressFinalize(this); + _activeModulesUpdateTimer?.Dispose(); } #endregion diff --git a/src/Artemis.UI/Screens/Splash/SplashViewModel.cs b/src/Artemis.UI/Screens/Splash/SplashViewModel.cs index dcb747bf6..a3c4ef364 100644 --- a/src/Artemis.UI/Screens/Splash/SplashViewModel.cs +++ b/src/Artemis.UI/Screens/Splash/SplashViewModel.cs @@ -81,12 +81,12 @@ namespace Artemis.UI.Screens.Splash Status = "Enabling plugin: " + args.Plugin.Info.Name; } - private void PluginManagementServiceOnPluginFeatureEnabling(object? sender, PluginFeatureEventArgs e) + private void PluginManagementServiceOnPluginFeatureEnabling(object sender, PluginFeatureEventArgs e) { Status = "Enabling: " + e.PluginFeature.GetType().Name.Humanize(); } - private void PluginManagementServiceOnPluginFeatureEnabled(object? sender, PluginFeatureEventArgs e) + private void PluginManagementServiceOnPluginFeatureEnabled(object sender, PluginFeatureEventArgs e) { Status = "Initializing UI"; } diff --git a/src/Artemis.UI/Services/LayerEditorService.cs b/src/Artemis.UI/Services/LayerEditorService.cs index 4141501c3..720c9f719 100644 --- a/src/Artemis.UI/Services/LayerEditorService.cs +++ b/src/Artemis.UI/Services/LayerEditorService.cs @@ -4,7 +4,6 @@ using System.Windows.Media; using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Services.Interfaces; -using Artemis.UI.Shared.Services; using SkiaSharp; using SkiaSharp.Views.WPF; @@ -14,7 +13,7 @@ namespace Artemis.UI.Services { private readonly ISettingsService _settingsService; - public LayerEditorService(ISettingsService settingsService, IProfileEditorService profileEditorService) + public LayerEditorService(ISettingsService settingsService) { _settingsService = settingsService; } @@ -93,11 +92,11 @@ namespace Artemis.UI.Services SKPath path = new SKPath(); path.AddRect(layerBounds); if (includeTranslation) - path.Transform(SKMatrix.MakeTranslation(x, y)); + path.Transform(SKMatrix.CreateTranslation(x, y)); if (includeScale) - path.Transform(SKMatrix.MakeScale(layer.Transform.Scale.CurrentValue.Width / 100f, layer.Transform.Scale.CurrentValue.Height / 100f, anchorPosition.X, anchorPosition.Y)); + path.Transform(SKMatrix.CreateScale(layer.Transform.Scale.CurrentValue.Width / 100f, layer.Transform.Scale.CurrentValue.Height / 100f, anchorPosition.X, anchorPosition.Y)); if (includeRotation) - path.Transform(SKMatrix.MakeRotationDegrees(layer.Transform.Rotation.CurrentValue, anchorPosition.X, anchorPosition.Y)); + path.Transform(SKMatrix.CreateRotationDegrees(layer.Transform.Rotation.CurrentValue, anchorPosition.X, anchorPosition.Y)); return path; } @@ -107,12 +106,10 @@ namespace Artemis.UI.Services { double renderScale = _settingsService.GetSetting("Core.RenderScale", 0.5).Value; if (absolute) - { return new SKPoint( 100f / layer.Bounds.Width * ((float) (point.X * renderScale) - layer.Bounds.Left) / 100f, 100f / layer.Bounds.Height * ((float) (point.Y * renderScale) - layer.Bounds.Top) / 100f ); - } return new SKPoint( 100f / layer.Bounds.Width * (float) (point.X * renderScale) / 100f, diff --git a/src/Artemis.UI/Services/RegistrationService.cs b/src/Artemis.UI/Services/RegistrationService.cs index abfb32371..cf4fc5ffc 100644 --- a/src/Artemis.UI/Services/RegistrationService.cs +++ b/src/Artemis.UI/Services/RegistrationService.cs @@ -74,7 +74,7 @@ namespace Artemis.UI.Services _registeredBuiltInPropertyEditors = true; } - private void PluginServiceOnPluginLoaded(object? sender, PluginEventArgs e) + private void PluginServiceOnPluginLoaded(object sender, PluginEventArgs e) { e.Plugin.Kernel.Load(new[] {new PluginUIModule(e.Plugin)}); } diff --git a/src/Artemis.UI/Stylet/FluentValidationAdapter.cs b/src/Artemis.UI/Stylet/FluentValidationAdapter.cs index a9c118a49..8ff5b8f7c 100644 --- a/src/Artemis.UI/Stylet/FluentValidationAdapter.cs +++ b/src/Artemis.UI/Stylet/FluentValidationAdapter.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Linq; -using System.Threading; using System.Threading.Tasks; using FluentValidation; using Stylet; @@ -26,7 +25,7 @@ namespace Artemis.UI.Stylet { // If someone's calling us synchronously, and ValidationAsync does not complete synchronously, // we'll deadlock unless we continue on another thread. - return (await _validator.ValidateAsync(_subject, CancellationToken.None, propertyName).ConfigureAwait(false)) + return (await _validator.ValidateAsync(_subject, options => options.IncludeProperties(propertyName)).ConfigureAwait(false)) .Errors.Select(x => x.ErrorMessage); }