1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Shared UI - Resolved all remaining warnings

UI - Resolved all remaining warnings
Layer properties - Fixed DisableKeyframes layer property attribute not being applied
This commit is contained in:
SpoinkyNL 2020-11-20 23:13:37 +01:00
parent 931b43aa66
commit 8d901027ee
75 changed files with 810 additions and 562 deletions

View File

@ -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;
}
/// <inheritdoc />

View File

@ -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();

View File

@ -13,20 +13,20 @@ namespace Artemis.UI.Shared
public class ColorToStringConverter : IValueConverter
{
/// <inheritdoc />
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();
}
/// <inheritdoc />
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;

View File

@ -14,18 +14,18 @@ namespace Artemis.UI.Shared
public class SKColorToStringConverter : IValueConverter
{
/// <inheritdoc />
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();
}
/// <inheritdoc />
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;
}
}
}

View File

@ -11,7 +11,7 @@ namespace Artemis.UI.Shared
public class TypeToStringConverter : IValueConverter
{
/// <inheritdoc />
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)

View File

@ -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
/// <typeparam name="T">The type of the data model</typeparam>
public abstract class DataModelDisplayViewModel<T> : DataModelDisplayViewModel
{
private T _displayValue;
[AllowNull]
private T _displayValue = default!;
/// <summary>
/// Gets or sets value that the view model must display
/// </summary>
[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();
/// <inheritdoc />
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
/// </summary>
public abstract class DataModelDisplayViewModel : PropertyChangedBase
{
private DataModelPropertyAttribute _propertyDescription;
private DataModelPropertyAttribute? _propertyDescription;
/// <summary>
/// Gets the property description of this value
/// </summary>
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
/// </summary>
/// <param name="model">The value to set</param>
public abstract void UpdateValue(object model);
public abstract void UpdateValue(object? model);
}
}

View File

@ -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<T> : DataModelInputViewModel
{
private bool _closed;
private T _inputValue;
[AllowNull] private T _inputValue = default!;
/// <summary>
/// Creates a new instance of the <see cref="DataModelInputViewModel{T}" /> class
@ -32,6 +33,7 @@ namespace Artemis.UI.Shared
/// <summary>
/// Gets or sets the value shown in the input
/// </summary>
[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<object, bool> UpdateCallback { get; set; }
internal Action<object?, bool> UpdateCallback { get; set; } = null!; // Set right after construction
/// <summary>
/// Gets the types this input view model can support through type conversion. This list is defined when registering the
/// view model.
/// </summary>
internal IReadOnlyCollection<Type> CompatibleConversionTypes { get; set; }
internal IReadOnlyCollection<Type>? CompatibleConversionTypes { get; set; }
/// <summary>
/// Submits the input value and removes this view model.
@ -133,6 +135,6 @@ namespace Artemis.UI.Shared
}
/// <inheritdoc />
public UIElement View { get; set; }
public UIElement? View { get; set; }
}
}

View File

@ -12,21 +12,25 @@ using Stylet;
namespace Artemis.UI.Shared.Input
{
/// <summary>
/// Represents a view model that allows selecting a data model property used by boolean operations on a certain data
/// model property
/// </summary>
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();
}
/// <summary>
/// Gets or sets the brush to use for the input button
/// </summary>
public SolidColorBrush ButtonBrush
{
get => _buttonBrush;
@ -49,26 +56,41 @@ namespace Artemis.UI.Shared.Input
}
}
/// <summary>
/// Gets the brush to use for the switch button
/// </summary>
public SolidColorBrush SwitchButtonBrush => new SolidColorBrush(ButtonBrush.Color.Darken());
/// <summary>
/// Gets or sets the placeholder text when no value is entered
/// </summary>
public string Placeholder
{
get => _placeholder;
set => SetAndNotify(ref _placeholder, value);
}
/// <summary>
/// Gets or sets the enabled state of the input
/// </summary>
public bool IsEnabled
{
get => _isEnabled;
set => SetAndNotify(ref _isEnabled, value);
}
/// <summary>
/// Gets or sets whether the switch button should be displayed
/// </summary>
public bool DisplaySwitchButton
{
get => _displaySwitchButton;
set => SetAndNotify(ref _displaySwitchButton, value);
}
/// <summary>
/// Gets or sets the types of properties this view model will allow to be selected
/// </summary>
public Type[] FilterTypes
{
get => _filterTypes;
@ -79,18 +101,38 @@ namespace Artemis.UI.Shared.Input
}
}
/// <summary>
/// Gets a bindable collection of extra data model view models to show
/// </summary>
public BindableCollection<DataModelPropertiesViewModel> ExtraDataModelViewModels { get; }
/// <summary>
/// Gets a boolean indicating whether there are any extra data models
/// </summary>
public bool HasExtraDataModels => ExtraDataModelViewModels.Any();
/// <summary>
/// Command used by view
/// </summary>
public DelegateCommand SelectPropertyCommand { get; }
/// <summary>
/// Setting used by view
/// </summary>
public PluginSetting<bool> ShowDataModelValues { get; }
public DataModelPropertiesViewModel DataModelViewModel
/// <summary>
/// Gets or sets root the data model view model
/// </summary>
public DataModelPropertiesViewModel? DataModelViewModel
{
get => _dataModelViewModel;
private set => SetAndNotify(ref _dataModelViewModel, value);
}
/// <summary>
/// Gets or sets a boolean indicating whether the data model is open
/// </summary>
public bool IsDataModelViewModelOpen
{
get => _isDataModelViewModelOpen;
@ -101,7 +143,10 @@ namespace Artemis.UI.Shared.Input
}
}
public DataModelPath DataModelPath
/// <summary>
/// Gets or sets the data model path of the currently selected data model property
/// </summary>
public DataModelPath? DataModelPath
{
get => _dataModelPath;
private set
@ -113,9 +158,19 @@ namespace Artemis.UI.Shared.Input
}
}
/// <summary>
/// Gets a boolean indicating whether the current selection is valid
/// </summary>
public bool IsValid => DataModelPath?.IsValid ?? true;
public string DisplayValue => DataModelPath?.GetPropertyDescription()?.Name ?? DataModelPath?.Segments.LastOrDefault()?.Identifier;
/// <summary>
/// Gets the display name of the currently selected property
/// </summary>
public string? DisplayValue => DataModelPath?.GetPropertyDescription()?.Name ?? DataModelPath?.Segments.LastOrDefault()?.Identifier;
/// <summary>
/// Gets the human readable path of the currently selected property
/// </summary>
public string DisplayPath
{
get
@ -128,8 +183,16 @@ namespace Artemis.UI.Shared.Input
}
}
/// <summary>
/// Gets or sets a boolean indicating whether event child VMs should be loaded, defaults to <see langword="true" />
/// </summary>
public bool LoadEventChildren { get; set; } = true;
/// <summary>
/// Changes the root data model VM stored in <see cref="DataModelViewModel" /> to the provided
/// <paramref name="dataModel" />
/// </summary>
/// <param name="dataModel">The data model VM to set the new root data model to</param>
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)
/// <summary>
/// Changes the currently selected property by its path
/// </summary>
/// <param name="dataModelPath">The path of the property to set the selection to</param>
public void ChangeDataModelPath(DataModelPath? dataModelPath)
{
DataModelPath?.Dispose();
DataModelPath = dataModelPath != null ? new DataModelPath(dataModelPath) : null;
}
/// <summary>
/// Requests switching the input type to static using a <see cref="DataModelStaticViewModel" />
/// </summary>
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
/// <summary>
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_updateTimer.Stop();
_updateTimer.Dispose();
_updateTimer.Elapsed -= OnUpdateTimerOnElapsed;
DataModelPath?.Dispose();
}
}
/// <inheritdoc />
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<DataModelInputDynamicEventArgs> PropertySelected;
public event EventHandler SwitchToStaticRequested;
/// <summary>
/// Occurs when anew property has been selected
/// </summary>
public event EventHandler<DataModelInputDynamicEventArgs>? PropertySelected;
/// <summary>
/// Occurs when a switch to static input has been requested
/// </summary>
public event EventHandler? SwitchToStaticRequested;
/// <summary>
/// Invokes the <see cref="PropertySelected" /> event
/// </summary>
/// <param name="e"></param>
protected virtual void OnPropertySelected(DataModelInputDynamicEventArgs e)
{
PropertySelected?.Invoke(this, e);
}
/// <summary>
/// Invokes the <see cref="SwitchToStaticRequested" /> event
/// </summary>
protected virtual void OnSwitchToStaticRequested()
{
SwitchToStaticRequested?.Invoke(this, EventArgs.Empty);

View File

@ -11,29 +11,33 @@ using Stylet;
namespace Artemis.UI.Shared.Input
{
/// <summary>
/// Represents a view model that allows inputting a static value used by boolean operations on a certain data model
/// property
/// </summary>
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<Window>().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
/// <summary>
/// Gets the view model used to display the value
/// </summary>
public DataModelDisplayViewModel DisplayViewModel
public DataModelDisplayViewModel? DisplayViewModel
{
get => _displayViewModel;
private set => SetAndNotify(ref _displayViewModel, value);
@ -72,7 +76,7 @@ namespace Artemis.UI.Shared.Input
/// <summary>
/// Gets the view model used to edit the value
/// </summary>
public DataModelInputViewModel InputViewModel
public DataModelInputViewModel? InputViewModel
{
get => _inputViewModel;
private set => SetAndNotify(ref _inputViewModel, value);
@ -99,7 +103,7 @@ namespace Artemis.UI.Shared.Input
/// <summary>
/// Gets or sets the value of the target
/// </summary>
public object Value
public object? Value
{
get => _value;
set
@ -173,7 +177,7 @@ namespace Artemis.UI.Shared.Input
}
/// <summary>
/// Requests switching the input type to dynamic
/// Requests switching the input type to dynamic using a <see cref="DataModelDynamicViewModel"/>
/// </summary>
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;
}
}
}
/// <inheritdoc />

View File

@ -6,21 +6,28 @@ using Artemis.UI.Shared.Services;
namespace Artemis.UI.Shared
{
/// <summary>
/// Represents a view model that visualizes an event data model property
/// </summary>
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
/// <summary>
/// Gets the type of event arguments this event triggers and that must be displayed as children
/// </summary>
public Type? DisplayValueType
{
get => _displayValueType;
set => SetAndNotify(ref _displayValueType, value);
}
public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration configuration)
/// <inheritdoc />
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()
/// <summary>
/// Always returns <see langword="null"/> for data model events
/// </summary>
public override object? GetCurrentValue()
{
return null;
}
/// <inheritdoc />
public override string ToString()
public override string? ToString()
{
return DisplayPath ?? Path;
}

View File

@ -5,43 +5,63 @@ using Artemis.UI.Shared.Services;
namespace Artemis.UI.Shared
{
/// <summary>
/// Represents a view model that wraps a regular <see cref="DataModelPropertiesViewModel" /> but contained in
/// a <see cref="DataModelListViewModel" />
/// </summary>
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;
}
/// <summary>
/// Gets the index of the element within the list
/// </summary>
public int Index
{
get => _index;
set => SetAndNotify(ref _index, value);
}
public Type ListType
/// <summary>
/// Gets the type of elements contained in the list
/// </summary>
public Type? ListType
{
get => _listType;
set => SetAndNotify(ref _listType, value);
}
public new object DisplayValue
/// <summary>
/// Gets the value of the property that is being visualized
/// </summary>
public new object? DisplayValue
{
get => _displayValue;
set => SetAndNotify(ref _displayValue, value);
}
public DataModelVisualizationViewModel DisplayViewModel => Children.FirstOrDefault();
/// <summary>
/// Gets the view model that handles displaying the property
/// </summary>
public DataModelVisualizationViewModel? DisplayViewModel => Children.FirstOrDefault();
public override string DisplayPath => null;
/// <inheritdoc />
public override string? DisplayPath => null;
public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration configuration)
/// <inheritdoc />
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()
/// <inheritdoc />
public override object? GetCurrentValue()
{
return DisplayValue;
}

View File

@ -10,19 +10,22 @@ namespace Artemis.UI.Shared
/// </summary>
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
/// <summary>
/// Gets the type of elements contained in the list
/// </summary>
public Type ListType
public Type? ListType
{
get => _listType;
private set => SetAndNotify(ref _listType, value);
}
/// <inheritdoc />
public override object GetCurrentValue()
public override object? GetCurrentValue()
{
return DisplayValue;
}
/// <inheritdoc />
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);

View File

@ -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<DataModelVisualizationViewModel> _listChildren;
private int _listCount;
internal DataModelListViewModel(DataModel dataModel, DataModelVisualizationViewModel parent, DataModelPath dataModelPath)
: base(dataModel, parent, dataModelPath)
{
ListChildren = new BindableCollection<DataModelVisualizationViewModel>();
_countDisplay = "0 items";
_listChildren = new BindableCollection<DataModelVisualizationViewModel>();
}
/// <summary>
/// Gets the instance of the list that is being visualized
/// </summary>
public IEnumerable List
public IEnumerable? List
{
get => _list;
private set => SetAndNotify(ref _list, value);
@ -45,7 +46,7 @@ namespace Artemis.UI.Shared
/// <summary>
/// Gets the type of elements this list contains and that must be displayed as children
/// </summary>
public Type DisplayValueType
public Type? DisplayValueType
{
get => _displayValueType;
set => SetAndNotify(ref _displayValueType, value);
@ -70,7 +71,7 @@ namespace Artemis.UI.Shared
}
/// <inheritdoc />
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

View File

@ -10,10 +10,10 @@ namespace Artemis.UI.Shared
/// </summary>
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
/// <summary>
/// Gets the type of the property that is being visualized
/// </summary>
public Type DisplayValueType
public Type? DisplayValueType
{
get => _displayValueType;
private set => SetAndNotify(ref _displayValueType, value);
@ -30,19 +30,19 @@ namespace Artemis.UI.Shared
/// <summary>
/// Gets the value of the property that is being visualized
/// </summary>
public object DisplayValue
public object? DisplayValue
{
get => _displayValue;
private set => SetAndNotify(ref _displayValue, value);
}
/// <inheritdoc />
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
}
/// <inheritdoc />
public override object GetCurrentValue()
public override object? GetCurrentValue()
{
if (Parent == null || Parent.IsRootViewModel || IsRootViewModel)
return DataModel;
@ -68,7 +68,7 @@ namespace Artemis.UI.Shared
}
/// <inheritdoc />
public override string ToString()
public override string? ToString()
{
return DisplayPath ?? Path;
}

View File

@ -11,11 +11,11 @@ namespace Artemis.UI.Shared
/// </summary>
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
/// <summary>
/// Gets the value of the property that is being visualized
/// </summary>
public object DisplayValue
public object? DisplayValue
{
get => _displayValue;
internal set => SetAndNotify(ref _displayValue, value);
@ -32,7 +32,7 @@ namespace Artemis.UI.Shared
/// <summary>
/// Gets the type of the property that is being visualized
/// </summary>
public Type DisplayValueType
public Type? DisplayValueType
{
get => _displayValueType;
protected set => SetAndNotify(ref _displayValueType, value);
@ -41,26 +41,31 @@ namespace Artemis.UI.Shared
/// <summary>
/// Gets the view model used to display the display value
/// </summary>
public DataModelDisplayViewModel DisplayViewModel
public DataModelDisplayViewModel? DisplayViewModel
{
get => _displayViewModel;
internal set => SetAndNotify(ref _displayViewModel, value);
}
/// <inheritdoc />
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);
}

View File

@ -17,24 +17,24 @@ namespace Artemis.UI.Shared
{
private const int MaxDepth = 4;
private BindableCollection<DataModelVisualizationViewModel> _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<DataModelVisualizationViewModel>();
_parent = parent;
DataModelPath = dataModelPath;
Children = new BindableCollection<DataModelVisualizationViewModel>();
IsMatchingFilteredTypes = true;
if (parent == null)
IsRootViewModel = true;
else
PropertyDescription = DataModelPath?.GetPropertyDescription() ?? DataModel.DataModelDescription;
PropertyDescription = DataModelPath?.GetPropertyDescription() ?? DataModel?.DataModelDescription;
}
/// <summary>
@ -45,12 +45,12 @@ namespace Artemis.UI.Shared
/// <summary>
/// Gets the data model path to the property this view model is visualizing
/// </summary>
public DataModelPath DataModelPath { get; }
public DataModelPath? DataModelPath { get; }
/// <summary>
/// Gets a string representation of the path backing this model
/// </summary>
public string Path => DataModelPath?.Path;
public string? Path => DataModelPath?.Path;
/// <summary>
/// Gets the property depth of the view model
@ -60,7 +60,7 @@ namespace Artemis.UI.Shared
/// <summary>
/// Gets the data model backing this view model
/// </summary>
public DataModel DataModel
public DataModel? DataModel
{
get => _dataModel;
protected set => SetAndNotify(ref _dataModel, value);
@ -69,7 +69,7 @@ namespace Artemis.UI.Shared
/// <summary>
/// Gets the property description of the property this view model is visualizing
/// </summary>
public DataModelPropertyAttribute PropertyDescription
public DataModelPropertyAttribute? PropertyDescription
{
get => _propertyDescription;
protected set => SetAndNotify(ref _propertyDescription, value);
@ -78,7 +78,7 @@ namespace Artemis.UI.Shared
/// <summary>
/// Gets the parent of this view model
/// </summary>
public DataModelVisualizationViewModel Parent
public DataModelVisualizationViewModel? Parent
{
get => _parent;
protected set => SetAndNotify(ref _parent, value);
@ -119,7 +119,7 @@ namespace Artemis.UI.Shared
/// <summary>
/// Gets a user-friendly representation of the <see cref="DataModelPath" />
/// </summary>
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
/// </summary>
/// <param name="dataModelUIService">The data model UI service used during update</param>
/// <param name="configuration">The configuration to apply while updating</param>
public abstract void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration configuration);
public abstract void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration);
/// <summary>
/// Gets the current value of the property being visualized
/// </summary>
/// <returns>The current value of the property being visualized</returns>
public virtual object GetCurrentValue()
public virtual object? GetCurrentValue()
{
if (IsRootViewModel)
return null;
return DataModelPath.GetValue();
return DataModelPath?.GetValue();
}
/// <summary>
@ -148,7 +148,7 @@ namespace Artemis.UI.Shared
/// </summary>
/// <param name="looseMatch">Whether the type may be a loose match, meaning it can be cast or converted</param>
/// <param name="filteredTypes">The types to filter</param>
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<DataModelIgnoreAttribute>() != 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<PropertyInfo> 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<PropertyInfo> 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<string, DataModel> 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<DataModelVisualizationViewModel> toRemoveDynamic = Children.Where(c => !c.DataModelPath.IsValid).ToList();
List<DataModelVisualizationViewModel> 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

View File

@ -9,7 +9,7 @@ namespace Artemis.UI.Shared
/// </summary>
public class DataModelInputDynamicEventArgs : EventArgs
{
internal DataModelInputDynamicEventArgs(DataModelPath dataModelPath)
internal DataModelInputDynamicEventArgs(DataModelPath? dataModelPath)
{
DataModelPath = dataModelPath;
}
@ -17,6 +17,6 @@ namespace Artemis.UI.Shared
/// <summary>
/// Gets the data model path that was selected
/// </summary>
public DataModelPath DataModelPath { get; }
public DataModelPath? DataModelPath { get; }
}
}

View File

@ -8,7 +8,7 @@ namespace Artemis.UI.Shared
/// </summary>
public class DataModelInputStaticEventArgs : EventArgs
{
internal DataModelInputStaticEventArgs(object value)
internal DataModelInputStaticEventArgs(object? value)
{
Value = value;
}
@ -16,6 +16,6 @@ namespace Artemis.UI.Shared
/// <summary>
/// The value that was submitted
/// </summary>
public object Value { get; }
public object? Value { get; }
}
}

View File

@ -8,12 +8,12 @@ namespace Artemis.UI.Shared
/// </summary>
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
/// <summary>
/// Gets the profile the event was raised for
/// </summary>
public Profile Profile { get; }
public Profile? Profile { get; }
/// <summary>
/// If applicable, the previous active profile before the event was raised

View File

@ -8,12 +8,12 @@ namespace Artemis.UI.Shared
/// </summary>
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
/// <summary>
/// Gets the profile element the event was raised for
/// </summary>
public RenderProfileElement RenderProfileElement { get; }
public RenderProfileElement? RenderProfileElement { get; }
/// <summary>
/// If applicable, the previous active profile element before the event was raised

View File

@ -15,11 +15,6 @@ namespace Artemis.UI.Shared.LayerBrushes
/// </summary>
public abstract class LayerBrushConfigurationDialog : ILayerBrushConfigurationDialog
{
/// <summary>
/// The layer brush this dialog belongs to
/// </summary>
internal BaseLayerBrush LayerBrush { get; set; }
/// <summary>
/// The type of view model the tab contains
/// </summary>

View File

@ -15,10 +15,11 @@ namespace Artemis.UI.Shared.LayerEffects
/// </summary>
public abstract class LayerEffectConfigurationDialog : ILayerEffectConfigurationDialog
{
// TODO: See if this is still in use
/// <summary>
/// The layer effect this dialog belongs to
/// </summary>
internal BaseLayerEffect LayerEffect { get; set; }
public BaseLayerEffect? LayerEffect { get; set; }
/// <summary>
/// The type of view model the tab contains

View File

@ -15,11 +15,6 @@ namespace Artemis.UI.Shared
/// </summary>
public abstract class PluginConfigurationDialog : IPluginConfigurationDialog
{
/// <summary>
/// The layer brush this dialog belongs to
/// </summary>
internal PluginFeature PluginFeature { get; set; }
/// <summary>
/// The type of view model the tab contains
/// </summary>

View File

@ -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

View File

@ -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<T> : PropertyInputViewModel
{
private bool _inputDragging;
private T _inputValue;
[AllowNull] private T _inputValue = default!;
/// <summary>
/// Creates a new instance of the <see cref="PropertyInputViewModel" /> class
@ -73,6 +74,7 @@ namespace Artemis.UI.Shared
/// <summary>
/// Gets or sets the input value
/// </summary>
[AllowNull]
public T InputValue
{
get => _inputValue;

View File

@ -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);
}
}

View File

@ -9,12 +9,12 @@ namespace Artemis.UI.Shared.Screens.Exceptions
{
private List<DialogException> _exceptions;
public ExceptionViewModel(string message, Exception exception)
public ExceptionViewModel(string message, Exception? exception)
{
Header = message;
Exceptions = new List<DialogException>();
_exceptions = new List<DialogException>();
Exception currentException = exception;
Exception? currentException = exception;
while (currentException != null)
{
Exceptions.Add(new DialogException(currentException));

View File

@ -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<Canvas>((DependencyObject) sender, null);
Canvas? parent = VisualTreeUtilities.FindParent<Canvas>((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<ColorGradientStop> 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)

View File

@ -14,7 +14,7 @@ namespace Artemis.UI.Shared.Screens.GradientEditor
{
private readonly List<ColorGradientStop> _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<ColorStopViewModel> 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<Canvas>((DependencyObject) sender, null);
Canvas? child = VisualTreeUtilities.FindChild<Canvas>((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<Canvas>((DependencyObject) sender, null);
Canvas? parent = VisualTreeUtilities.FindParent<Canvas>((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))

View File

@ -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;

View File

@ -46,7 +46,9 @@ namespace Artemis.UI.Shared.Services
public void UpdateModules(DataModelPropertiesViewModel mainDataModelVisualization)
{
List<DataModelVisualizationViewModel> disabledChildren = mainDataModelVisualization.Children.Where(d => !d.DataModel.Feature.IsEnabled).ToList();
List<DataModelVisualizationViewModel> 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<T>(Plugin plugin, IReadOnlyCollection<Type> compatibleConversionTypes = null) where T : DataModelInputViewModel
public DataModelVisualizationRegistration RegisterDataModelInput<T>(Plugin plugin, IReadOnlyCollection<Type>? compatibleConversionTypes = null) where T : DataModelInputViewModel
{
if (compatibleConversionTypes == null)
compatibleConversionTypes = new List<Type>();
compatibleConversionTypes ??= new List<Type>();
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<object, bool> updateCallback)
public DataModelInputViewModel? GetDataModelInputViewModel(Type propertyType, DataModelPropertyAttribute? description, object? initialValue, Action<object?, bool> 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;

View File

@ -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<object> ShowDialog<T>(IParameter[] parameters) where T : DialogViewModelBase
{
if (parameters == null) throw new ArgumentNullException(nameof(parameters));
return await ShowDialog("RootDialog", _kernel.Get<T>(parameters));
return await ShowDialog("RootDialog", GetBestKernel().Get<T>(parameters));
}
private async Task<object> ShowDialog(string identifier, DialogViewModelBase viewModel)
private async Task<object> ShowDialog(string? identifier, DialogViewModelBase viewModel)
{
Task<object> result = null;
Task<object>? 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<object> ShowDialogAt<T>(string identifier, IParameter[] parameters) where T : DialogViewModelBase
{
if (parameters == null)
throw new ArgumentNullException(nameof(parameters));
return await ShowDialog(identifier, GetBestKernel().Get<T>(parameters));
}
public async Task<bool> 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<bool> 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<object> ShowDialog<T>() where T : DialogViewModelBase
{
return await ShowDialog("RootDialog", _kernel.Get<T>());
return await ShowDialog("RootDialog", GetBestKernel().Get<T>());
}
public Task<object> ShowDialog<T>(Dictionary<string, object> parameters) where T : DialogViewModelBase
@ -95,32 +104,28 @@ namespace Artemis.UI.Shared.Services
public async Task<object> ShowDialogAt<T>(string identifier) where T : DialogViewModelBase
{
return await ShowDialog(identifier, _kernel.Get<T>());
if (identifier == null) throw new ArgumentNullException(nameof(identifier));
return await ShowDialog(identifier, GetBestKernel().Get<T>());
}
public async Task<object> ShowDialogAt<T>(string identifier, Dictionary<string, object> 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<IParameter>().ToArray();
return await ShowDialogAt<T>(identifier, paramsArray);
}
private async Task<object> ShowDialogAt<T>(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<T>(parameters));
return await ShowDialog(identifier, _kernel.Get<T>(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;
}
}
}

View File

@ -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)

View File

@ -37,7 +37,7 @@ namespace Artemis.UI.Shared.Services
/// <param name="pluginFeature">The plugin feature to create hte data model visualization view model for</param>
/// <param name="includeMainDataModel">Whether or not also to include the main data model</param>
/// <returns>A data model visualization view model containing the data model of the provided feature</returns>
DataModelPropertiesViewModel GetPluginDataModelVisualization(PluginFeature pluginFeature, bool includeMainDataModel);
DataModelPropertiesViewModel? GetPluginDataModelVisualization(PluginFeature pluginFeature, bool includeMainDataModel);
/// <summary>
/// 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
/// </param>
/// <returns>The most appropriate display view model for the provided <paramref name="propertyType"></paramref></returns>
DataModelDisplayViewModel GetDataModelDisplayViewModel(Type propertyType, DataModelPropertyAttribute description, bool fallBackToDefault = false);
DataModelDisplayViewModel? GetDataModelDisplayViewModel(Type propertyType, DataModelPropertyAttribute? description, bool fallBackToDefault = false);
/// <summary>
/// Creates the most appropriate input view model for the provided <paramref name="propertyType" /> that allows
@ -100,7 +100,7 @@ namespace Artemis.UI.Shared.Services
/// <param name="initialValue">The initial value to show in the input</param>
/// <param name="updateCallback">A function to call whenever the input was updated (submitted or not)</param>
/// <returns>The most appropriate input view model for the provided <paramref name="propertyType" /></returns>
DataModelInputViewModel GetDataModelInputViewModel(Type propertyType, DataModelPropertyAttribute description, object initialValue, Action<object, bool> updateCallback);
DataModelInputViewModel? GetDataModelInputViewModel(Type propertyType, DataModelPropertyAttribute? description, object? initialValue, Action<object?, bool> updateCallback);
/// <summary>
/// Creates a view model that allows selecting a value from the data model

View File

@ -14,17 +14,17 @@ namespace Artemis.UI.Shared.Services
/// <summary>
/// Gets the currently selected profile
/// </summary>
Profile SelectedProfile { get; }
Profile? SelectedProfile { get; }
/// <summary>
/// Gets the currently selected profile element
/// </summary>
RenderProfileElement SelectedProfileElement { get; }
RenderProfileElement? SelectedProfileElement { get; }
/// <summary>
/// Gets the currently selected data binding property
/// </summary>
ILayerProperty SelectedDataBinding { get; }
ILayerProperty? SelectedDataBinding { get; }
/// <summary>
/// Gets or sets the current time
@ -45,7 +45,7 @@ namespace Artemis.UI.Shared.Services
/// Changes the selected profile
/// </summary>
/// <param name="profile">The profile to select</param>
void ChangeSelectedProfile(Profile profile);
void ChangeSelectedProfile(Profile? profile);
/// <summary>
/// Updates the selected profile and saves it to persistent storage
@ -56,7 +56,7 @@ namespace Artemis.UI.Shared.Services
/// Changes the selected profile element
/// </summary>
/// <param name="profileElement">The profile element to select</param>
void ChangeSelectedProfileElement(RenderProfileElement profileElement);
void ChangeSelectedProfileElement(RenderProfileElement? profileElement);
/// <summary>
/// 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
/// </summary>
/// <param name="layerProperty">The data binding property to select</param>
void ChangeSelectedDataBinding(ILayerProperty layerProperty);
void ChangeSelectedDataBinding(ILayerProperty? layerProperty);
/// <summary>
/// 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
/// </summary>
/// <returns>The current module the profile editor is initialized for</returns>
ProfileModule GetCurrentModule();
ProfileModule? GetCurrentModule();
/// <summary>
/// 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 <see cref="PropertyInputViewModel{T}" /> supporting
/// <typeparamref name="T" />
/// </summary>
PropertyInputViewModel<T> CreatePropertyInputViewModel<T>(LayerProperty<T> layerProperty);
PropertyInputViewModel<T>? CreatePropertyInputViewModel<T>(LayerProperty<T> layerProperty);
}
}

View File

@ -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<PropertyInputRegistration> 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<TimeSpan> snapTimes = null)
public TimeSpan SnapToTimeline(TimeSpan time, TimeSpan tolerance, bool snapToSegments, bool snapToCurrentTime, List<TimeSpan>? 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<T> CreatePropertyInputViewModel<T>(LayerProperty<T> layerProperty)
public PropertyInputViewModel<T>? CreatePropertyInputViewModel<T>(LayerProperty<T> 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<T>) 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<ProfileEventArgs> ProfileSelected;
public event EventHandler<ProfileEventArgs> SelectedProfileUpdated;
public event EventHandler<RenderProfileElementEventArgs> ProfileElementSelected;
public event EventHandler<RenderProfileElementEventArgs> 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<ProfileEventArgs>? ProfileSelected;
public event EventHandler<ProfileEventArgs>? SelectedProfileUpdated;
public event EventHandler<RenderProfileElementEventArgs>? ProfileElementSelected;
public event EventHandler<RenderProfileElementEventArgs>? 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
}
}

View File

@ -9,8 +9,8 @@ namespace Artemis.UI.Shared
/// </summary>
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)!;
/// <summary>
/// Creates a shortcut
@ -24,7 +24,10 @@ namespace Artemis.UI.Shared
/// <param name="iconPath">The icon path of the shortcut</param>
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)]

View File

@ -20,12 +20,12 @@ namespace Artemis.UI.Shared
/// If not matching item can be found,
/// a null parent is being returned.
/// </returns>
public static T FindChild<T>(DependencyObject parent, string childName) where T : DependencyObject
public static T? FindChild<T>(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.
/// </returns>
public static T FindParent<T>(DependencyObject child, string parentName) where T : DependencyObject
public static T? FindParent<T>(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)

View File

@ -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<UIModule>();
@ -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();
});
}
}
}

View File

@ -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
/// <inheritdoc />
public void Dispose()
{
_updateTimer?.Dispose();
}
#endregion
}
}

View File

@ -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<BaseConditionOperator> _operators;
private DataModelStaticViewModel _rightSideInputViewModel;
private DataModelDynamicViewModel _rightSideSelectionViewModel;
private BaseConditionOperator _selectedOperator;
private List<Type> _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();
}
}
/// <inheritdoc />
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();

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -10,7 +10,7 @@ using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.ConditionalDataBinding
{
public class ConditionalDataBindingModeViewModel<TLayerProperty, TProperty> : Conductor<DataBindingConditionViewModel<TLayerProperty, TProperty>>.Collection.AllActive, IDataBindingModeViewModel
public sealed class ConditionalDataBindingModeViewModel<TLayerProperty, TProperty> : Conductor<DataBindingConditionViewModel<TLayerProperty, TProperty>>.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
/// <inheritdoc />
public void Dispose()
{
ConditionalDataBinding.ConditionsUpdated -= ConditionalDataBindingOnConditionsUpdated;

View File

@ -10,11 +10,11 @@ using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.ConditionalDataBinding
{
public class DataBindingConditionViewModel<TLayerProperty, TProperty> : Conductor<DataModelConditionGroupViewModel>, IDisposable
public sealed class DataBindingConditionViewModel<TLayerProperty, TProperty> : Conductor<DataModelConditionGroupViewModel>, IDisposable
{
private readonly IProfileEditorService _profileEditorService;
private readonly IDataModelConditionsVmFactory _dataModelConditionsVmFactory;
private readonly IDataModelUIService _dataModelUIService;
private readonly IProfileEditorService _profileEditorService;
public DataBindingConditionViewModel(DataBindingCondition<TLayerProperty, TProperty> 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
/// <inheritdoc />
public void Dispose()
{
ValueViewModel?.Dispose();
}
#endregion
}
}

View File

@ -10,17 +10,17 @@ using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
{
public class DataBindingViewModel<TLayerProperty, TProperty> : Conductor<IDataBindingModeViewModel>, IDataBindingViewModel
public sealed class DataBindingViewModel<TLayerProperty, TProperty> : Conductor<IDataBindingModeViewModel>, 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<TLayerProperty, TProperty> Registration { get; }
public BindableCollection<ValueDescription> 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<TLayerProperty, TProperty> _:
SelectedDataBindingMode = DataBindingModeType.Direct;
break;
case ConditionalDataBinding<TLayerProperty, TProperty> _:
SelectedDataBindingMode = DataBindingModeType.Conditional;
break;
default:
SelectedDataBindingMode = DataBindingModeType.None;
break;
}
DirectDataBinding<TLayerProperty, TProperty> _ => DataBindingModeType.Direct,
ConditionalDataBinding<TLayerProperty, TProperty> _ => 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<TLayerProperty> e)
private void LayerPropertyOnUpdated(object sender, LayerPropertyEventArgs<TLayerProperty> e)
{
if (ApplyTestResultToLayer)
if (ApplyTestResultToLayer)
Registration.DataBinding?.Apply();
}
#region IDisposable
/// <inheritdoc />
public void Dispose()
{
_updateTimer.Dispose();
_updateTimer.Elapsed -= OnUpdateTimerOnElapsed;
Registration.LayerProperty.Updated -= LayerPropertyOnUpdated;
}
#endregion
}
public interface IDataBindingViewModel : IScreen, IDisposable

View File

@ -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();
}

View File

@ -12,7 +12,7 @@ using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDataBinding
{
public class DataBindingModifierViewModel<TLayerProperty, TProperty> : PropertyChangedBase, IDisposable
public sealed class DataBindingModifierViewModel<TLayerProperty, TProperty> : PropertyChangedBase, IDisposable
{
private readonly IDataBindingService _dataBindingService;
private readonly IDataModelUIService _dataModelUIService;
@ -161,6 +161,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDa
#region IDisposable
/// <inheritdoc />
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();

View File

@ -12,7 +12,7 @@ using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDataBinding
{
public class DirectDataBindingModeViewModel<TLayerProperty, TProperty> : Screen, IDataBindingModeViewModel
public sealed class DirectDataBindingModeViewModel<TLayerProperty, TProperty> : Screen, IDataBindingModeViewModel
{
private readonly IDataBindingsVmFactory _dataBindingsVmFactory;
private readonly IDataModelUIService _dataModelUIService;
@ -70,6 +70,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDa
#region IDisposable
/// <inheritdoc />
public void Dispose()
{
TargetSelectionViewModel.PropertySelected -= TargetSelectionViewModelOnPropertySelected;

View File

@ -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
/// <inheritdoc />
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
/// </summary>
/// <param name="start">The position at which to start removing keyframes, if null this will start at the first keyframe</param>
/// <param name="end">The position at which to start removing keyframes, if null this will end at the last keyframe</param>
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)

View File

@ -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;

View File

@ -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;

View File

@ -7,11 +7,10 @@ using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
{
public class TimelineKeyframeViewModel<T> : Screen, ITimelineKeyframeViewModel
public sealed class TimelineKeyframeViewModel<T> : Screen, ITimelineKeyframeViewModel
{
private readonly IProfileEditorService _profileEditorService;
private BindableCollection<TimelineEasingViewModel> _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();
}

View File

@ -7,7 +7,7 @@ using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
{
public class TimelinePropertyViewModel<T> : Conductor<TimelineKeyframeViewModel<T>>.Collection.AllActive, ITimelinePropertyViewModel
public sealed class TimelinePropertyViewModel<T> : Conductor<TimelineKeyframeViewModel<T>>.Collection.AllActive, ITimelinePropertyViewModel
{
private readonly IProfileEditorService _profileEditorService;
private double _width;

View File

@ -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<LayerPropertyGroupViewModel> layerPropertyGroups,
IProfileEditorService profileEditorService, IDialogService dialogService)
@ -139,6 +138,40 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
await _dialogService.ShowDialog<TimelineSegmentDialogViewModel>(new Dictionary<string, object> {{"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

View File

@ -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
}
}

View File

@ -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<T> : Screen, ITreePropertyViewModel
public sealed class TreePropertyViewModel<T> : Screen, ITreePropertyViewModel
{
private readonly IProfileEditorService _profileEditorService;
private PropertyInputViewModel<T> _propertyInputViewModel;
@ -26,7 +25,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree
LayerProperty.DataBindingDisabled += LayerPropertyOnDataBindingChange;
LayerPropertyViewModel.IsVisible = !LayerProperty.IsHidden;
}
public LayerProperty<T> 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<T>(
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<T>(
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<T> e)
private void LayerPropertyOnVisibilityChanged(object sender, LayerPropertyEventArgs<T> e)
{
LayerPropertyViewModel.IsVisible = !LayerProperty.IsHidden;
}
private void LayerPropertyOnDataBindingChange(object? sender, LayerPropertyEventArgs<T> e)
private void LayerPropertyOnDataBindingChange(object sender, LayerPropertyEventArgs<T> e)
{
NotifyOfPropertyChange(nameof(HasDataBinding));
}

View File

@ -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);
}

View File

@ -16,9 +16,9 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem
public abstract class TreeItemViewModel : Conductor<TreeItemViewModel>.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<TreeItemViewModel> GetAllChildren()
{
List<TreeItemViewModel> children = new List<TreeItemViewModel>();
@ -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<TreeItemViewModel> newChildren = new List<TreeItemViewModel>();
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();
}
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}

View File

@ -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);

View File

@ -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);

View File

@ -20,22 +20,22 @@ using Stylet;
namespace Artemis.UI.Screens
{
public class RootViewModel : Conductor<IScreen>
public sealed class RootViewModel : Conductor<IScreen>, IDisposable
{
private readonly IRegistrationService _builtInRegistrationService;
private readonly PluginSetting<ApplicationColorScheme> _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> _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>();
AssemblyInformationalVersionAttribute versionAttribute = typeof(RootViewModel).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
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
/// <inheritdoc />
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 () =>

View File

@ -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<Module> _modules;
private string _propertySearch;
private Module _selectedModule;
@ -69,6 +69,17 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs
}
}
#region Overrides of Screen
/// <inheritdoc />
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
/// <inheritdoc />
public void Dispose()
{
_updateTimer?.Dispose();
}
#endregion
}
}

View File

@ -11,7 +11,6 @@ namespace Artemis.UI.Screens.Settings.Tabs.Devices
{
private readonly ISettingsVmFactory _settingsVmFactory;
private readonly ISurfaceService _surfaceService;
private BindableCollection<DeviceSettingsViewModel> _deviceSettingsViewModels;
public DeviceSettingsTabViewModel(ISurfaceService surfaceService, ISettingsVmFactory settingsVmFactory)
{

View File

@ -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;

View File

@ -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));
}

View File

@ -22,7 +22,7 @@ using Stylet;
namespace Artemis.UI.Screens.Sidebar
{
public class SidebarViewModel : PropertyChangedBase, IHandle<RequestSelectSidebarItemEvent>, IDisposable
public sealed class SidebarViewModel : PropertyChangedBase, IHandle<RequestSelectSidebarItemEvent>, 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

View File

@ -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";
}

View File

@ -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,

View File

@ -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)});
}

View File

@ -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);
}