From 353eec2529c2ae572d8de848f5b0fd4b0391268c Mon Sep 17 00:00:00 2001 From: Robert Date: Sun, 2 Jan 2022 23:56:06 +0100 Subject: [PATCH] Meta - Fix remaing warnings in non-WPF projects --- .../Artemis.UI.Avalonia.Shared.xml | 261 ++++++++++++++++-- .../Controls/HotkeyBox.axaml.cs | 18 +- .../Converters/ColorToSKColorConverter.cs | 4 +- .../Converters/EnumToBooleanConverter.cs | 4 +- .../Converters/SKColorToColorConverter.cs | 4 +- .../Converters/TypeToStringConverter.cs | 2 +- .../DefaultDataModelDisplayView.axaml.cs | 11 +- .../DefaultDataModelDisplayViewModel.cs | 1 + .../Services/Builders/ContentDialogBuilder.cs | 108 +++++++- .../Services/Builders/NotificationBuilder.cs | 38 ++- .../Builders/OpenFileDialogBuilder.cs | 6 +- .../Builders/SaveFileDialogBuilder.cs | 6 +- .../Interfaces/INotificationService.cs | 7 + .../Services/Interfaces/IWindowService.cs | 34 ++- .../MainWindowService/IMainWindowService.cs | 3 + .../Services/NotificationService.cs | 9 +- .../WindowService/ExceptionDialogViewModel.cs | 3 + .../Services/WindowService/WindowService.cs | 31 +-- .../ApplicationStateManager.cs | 6 +- .../Artemis.UI.Windows.csproj | 1 + .../Providers/Input/WindowsInputProvider.cs | 10 +- .../Utilities/ProcessUtilities.cs | 4 +- .../ColorToSolidColorBrushConverter.cs | 4 +- .../Converters/EnumToCollectionConverter.cs | 47 ---- .../Converters/LedIdToStringConverter.cs | 6 +- .../NormalizedPercentageConverter.cs | 4 +- .../Converters/UriToFileNameConverter.cs | 4 +- .../Converters/ValuesAdditionConverter.cs | 4 +- .../Tabs/Render/RenderDebugViewModel.cs | 4 +- .../Device/Tabs/DeviceInfoTabViewModel.cs | 3 + .../Device/Tabs/InputMappingsTabViewModel.cs | 6 +- .../Artemis.UI/Screens/Root/RootViewModel.cs | 8 +- .../Settings/Tabs/PluginsTabViewModel.cs | 2 +- .../ProfileConfigurationEditViewModel.cs | 26 +- .../Sidebar/SidebarCategoryViewModel.cs | 13 +- .../Screens/Sidebar/SidebarViewModel.cs | 8 +- .../SurfaceEditor/SurfaceDeviceViewModel.cs | 1 + 37 files changed, 539 insertions(+), 172 deletions(-) delete mode 100644 src/Avalonia/Artemis.UI/Converters/EnumToCollectionConverter.cs diff --git a/src/Artemis.UI.Avalonia.Shared/Artemis.UI.Avalonia.Shared.xml b/src/Artemis.UI.Avalonia.Shared/Artemis.UI.Avalonia.Shared.xml index 9f20fca3a..dd63d4ca4 100644 --- a/src/Artemis.UI.Avalonia.Shared/Artemis.UI.Avalonia.Shared.xml +++ b/src/Artemis.UI.Avalonia.Shared/Artemis.UI.Avalonia.Shared.xml @@ -129,15 +129,33 @@ Gets or sets the currently displayed icon as either a or an - pointing - to an SVG + pointing to an SVG + + + + + Gets or sets the watermark of the hotkey box when it is empty. + + + + + Gets or sets a boolean indicating whether the watermark should float above the hotkey box when it is not empty. Gets or sets the currently displayed icon as either a or an - pointing - to an SVG + pointing to an SVG + + + + + Gets or sets the watermark of the hotkey box when it is empty. + + + + + Gets or sets a boolean indicating whether the watermark should float above the hotkey box when it is not empty. @@ -704,6 +722,16 @@ + + + Represents a default data model display view. + + + + + Creates a new instance of the class. + + Represents the default data model display view model that is used when no display viewmodel specific for the type @@ -824,6 +852,127 @@ Occurs when the the window hosting the view model should close + + + Represents a builder that can be used to create Fluent UI dialogs. + + + + + Changes the title of the dialog. + + The new title. + The builder that can be used to further build the dialog. + + + + Changes the content of the dialog. + + The new content. + The builder that can be used to further build the dialog. + + + + Changes the default button of the dialog that is pressed on enter. + + The default button. + The builder that can be used to further build the dialog. + + + + Changes the primary button of the dialog. + + An action to configure the button. + The builder that can be used to further build the dialog. + + + + Changes the secondary button of the dialog. + + An action to configure the button. + The builder that can be used to further build the dialog. + + + + Changes the text of the close button of the dialog. + + The new text. + The builder that can be used to further build the dialog. + + + + Changes the view model of the content dialog, hosting it inside the dialog. + + The type of the view model to host. + The resulting view model. + Optional parameters to pass to the constructor of the view model, case and order sensitive. + The builder that can be used to further build the dialog. + + + + Asynchronously shows the content dialog. + + A task containing the result of the content dialog. + Thrown when the parent window does not contain a panel at its root. + + + + Represents a content dialog button. + + + + + No button. + + + + + The primary button. + + + + + The secondary button. + + + + + The close button. + + + + + Represents a builder that can be used to create buttons inside content dialogs. + + + + + Changes text message of the button. + + The new text. + The notification builder that can be used to further build the button. + + + + Changes action that is called when the button is clicked. + + The action to call when the button is clicked. + The builder that can be used to further build the button. + + + + Changes command that is called when the button is clicked. + + The command to call when the button is clicked. + The builder that can be used to further build the button. + + + + Changes parameter of the command that is called when the button is clicked. + + The parameter of the command to call when the button is clicked. + The builder that can be used to further build the button. + Represents a builder that can create a . @@ -894,7 +1043,7 @@ - Changes the action button of the dialog. + Changes the action button of the notification. An action to configure the button. The notification builder that can be used to further build the notification. @@ -921,20 +1070,58 @@ Changes action that is called when the button is clicked. The action to call when the button is clicked. - The notification builder that can be used to further build the button. + The builder that can be used to further build the button. Changes command that is called when the button is clicked. The command to call when the button is clicked. - The notification builder that can be used to further build the button. + The builder that can be used to further build the button. + + + + Changes parameter of the command that is called when the button is clicked. + + The parameter of the command to call when the button is clicked. + The builder that can be used to further build the button. + + + + Represents a severity of a notification. + + + + + A severity for informational messages. + + + + + A severity for success messages. + + + + + A severity for warning messages. + + + + + A severity for error messages. + Represents a builder that can create a . + + + Creates a new instance of the class. + + The parent window that will host the dialog. + Indicate that the user can select multiple files. @@ -962,7 +1149,7 @@ - Shows the file dialog + Asynchronously shows the file dialog. A task that on completion returns an array containing the full path to the selected @@ -974,6 +1161,12 @@ Represents a builder that can create a . + + + Creates a new instance of the class. + + The parent window that will host the notification. + Set the title of the dialog @@ -1001,7 +1194,7 @@ - Shows the save file dialog. + Asynchronously shows the save file dialog. A task that on completion contains the full path of the save location, or null if the @@ -1111,29 +1304,46 @@ A function to call whenever the input was updated (submitted or not) The most appropriate input view model for the provided + + + A service that can be used to create notifications in either the application or on the desktop. + + + + + Creates an in-app notification using a builder. + + A builder used to configure and show the notification. + + + + A service that can be used to show windows and dialogs. + + - Creates a view model instance of type and shows its corresponding View as a window + Creates a view model instance of type and shows its corresponding View as a + window The type of view model to create The created view model - Given a ViewModel, show its corresponding View as a window + Given a ViewModel, show its corresponding View as a window ViewModel to show the View for - Shows a dialog displaying the given exception + Shows a dialog displaying the given exception The title of the dialog The exception to display - Given an existing ViewModel, show its corresponding View as a Dialog + Given an existing ViewModel, show its corresponding View as a Dialog The return type ViewModel to show the View for @@ -1141,7 +1351,8 @@ - Creates a view model instance of type and shows its corresponding View as a Dialog + Creates a view model instance of type and shows its corresponding View as a + Dialog The view model type The return type @@ -1149,35 +1360,38 @@ - Shows a content dialog asking the user to confirm an action + Shows a content dialog asking the user to confirm an action The title of the dialog The message of the dialog The text of the confirm button - The text of the cancel button, if the cancel button will not be shown - A task containing the result of the dialog, if confirmed; otherwise + The text of the cancel button, if the cancel button will not be shown + + A task containing the result of the dialog, if confirmed; otherwise + + - Creates an open file dialog, use the fluent API to configure it + Creates an open file dialog, use the fluent API to configure it The builder that can be used to configure the dialog - Creates a save file dialog, use the fluent API to configure it + Creates a save file dialog, use the fluent API to configure it The builder that can be used to configure the dialog - Creates a content dialog, use the fluent API to configure it + Creates a content dialog, use the fluent API to configure it The builder that can be used to configure the dialog - Gets the current window of the application + Gets the current window of the application The current window of the application @@ -1212,6 +1426,11 @@ Occurs when the main window has been closed + + + A service that can be used to manage the state of the main window. + + Gets a boolean indicating whether the main window is currently open diff --git a/src/Avalonia/Artemis.UI.Shared/Controls/HotkeyBox.axaml.cs b/src/Avalonia/Artemis.UI.Shared/Controls/HotkeyBox.axaml.cs index 1b1983b75..af95c86e1 100644 --- a/src/Avalonia/Artemis.UI.Shared/Controls/HotkeyBox.axaml.cs +++ b/src/Avalonia/Artemis.UI.Shared/Controls/HotkeyBox.axaml.cs @@ -88,22 +88,26 @@ namespace Artemis.UI.Shared.Controls /// /// Gets or sets the currently displayed icon as either a or an - /// pointing - /// to an SVG + /// pointing to an SVG /// public static readonly StyledProperty HotkeyProperty = AvaloniaProperty.Register(nameof(Hotkey), defaultBindingMode: BindingMode.TwoWay, notifying: HotkeyChanging); + /// + /// Gets or sets the watermark of the hotkey box when it is empty. + /// public static readonly StyledProperty WatermarkProperty = AvaloniaProperty.Register(nameof(Watermark)); + /// + /// Gets or sets a boolean indicating whether the watermark should float above the hotkey box when it is not empty. + /// public static readonly StyledProperty UseFloatingWatermarkProperty = AvaloniaProperty.Register(nameof(UseFloatingWatermark)); /// /// Gets or sets the currently displayed icon as either a or an - /// pointing - /// to an SVG + /// pointing to an SVG /// public Hotkey? Hotkey { @@ -111,12 +115,18 @@ namespace Artemis.UI.Shared.Controls set => SetValue(HotkeyProperty, value); } + /// + /// Gets or sets the watermark of the hotkey box when it is empty. + /// public string? Watermark { get => GetValue(WatermarkProperty); set => SetValue(WatermarkProperty, value); } + /// + /// Gets or sets a boolean indicating whether the watermark should float above the hotkey box when it is not empty. + /// public bool UseFloatingWatermark { get => GetValue(UseFloatingWatermarkProperty); diff --git a/src/Avalonia/Artemis.UI.Shared/Converters/ColorToSKColorConverter.cs b/src/Avalonia/Artemis.UI.Shared/Converters/ColorToSKColorConverter.cs index bbd323daa..b09052e65 100644 --- a/src/Avalonia/Artemis.UI.Shared/Converters/ColorToSKColorConverter.cs +++ b/src/Avalonia/Artemis.UI.Shared/Converters/ColorToSKColorConverter.cs @@ -13,7 +13,7 @@ namespace Artemis.UI.Shared.Converters public class ColorToSKColorConverter : IValueConverter { /// - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { if (value is Color avaloniaColor) return new SKColor(avaloniaColor.R, avaloniaColor.G, avaloniaColor.B, avaloniaColor.A); @@ -24,7 +24,7 @@ namespace Artemis.UI.Shared.Converters } /// - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { Color result = new(0, 0, 0, 0); if (value is SKColor skColor) diff --git a/src/Avalonia/Artemis.UI.Shared/Converters/EnumToBooleanConverter.cs b/src/Avalonia/Artemis.UI.Shared/Converters/EnumToBooleanConverter.cs index 3c7b00239..eb0c950d5 100644 --- a/src/Avalonia/Artemis.UI.Shared/Converters/EnumToBooleanConverter.cs +++ b/src/Avalonia/Artemis.UI.Shared/Converters/EnumToBooleanConverter.cs @@ -11,13 +11,13 @@ namespace Artemis.UI.Shared.Converters public class EnumToBooleanConverter : IValueConverter { /// - public object Convert(object? value, Type targetType, object parameter, CultureInfo culture) + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { return Equals(value, parameter); } /// - public object ConvertBack(object? value, Type targetType, object parameter, CultureInfo culture) + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { return value?.Equals(true) == true ? parameter : BindingOperations.DoNothing; } diff --git a/src/Avalonia/Artemis.UI.Shared/Converters/SKColorToColorConverter.cs b/src/Avalonia/Artemis.UI.Shared/Converters/SKColorToColorConverter.cs index ae1143a9e..5f0c7c454 100644 --- a/src/Avalonia/Artemis.UI.Shared/Converters/SKColorToColorConverter.cs +++ b/src/Avalonia/Artemis.UI.Shared/Converters/SKColorToColorConverter.cs @@ -13,7 +13,7 @@ namespace Artemis.UI.Shared.Converters public class SKColorToColorConverter : IValueConverter { /// - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { Color result = new(0, 0, 0, 0); if (value is SKColor skColor) @@ -25,7 +25,7 @@ namespace Artemis.UI.Shared.Converters } /// - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { if (value is Color avaloniaColor) return new SKColor(avaloniaColor.R, avaloniaColor.G, avaloniaColor.B, avaloniaColor.A); diff --git a/src/Avalonia/Artemis.UI.Shared/Converters/TypeToStringConverter.cs b/src/Avalonia/Artemis.UI.Shared/Converters/TypeToStringConverter.cs index 6dd6149eb..8bec6ce70 100644 --- a/src/Avalonia/Artemis.UI.Shared/Converters/TypeToStringConverter.cs +++ b/src/Avalonia/Artemis.UI.Shared/Converters/TypeToStringConverter.cs @@ -21,7 +21,7 @@ namespace Artemis.UI.Shared.Converters } /// - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { throw new NotImplementedException(); } diff --git a/src/Avalonia/Artemis.UI.Shared/DefaultTypes/DataModel/Display/DefaultDataModelDisplayView.axaml.cs b/src/Avalonia/Artemis.UI.Shared/DefaultTypes/DataModel/Display/DefaultDataModelDisplayView.axaml.cs index e00449f1c..b956fe264 100644 --- a/src/Avalonia/Artemis.UI.Shared/DefaultTypes/DataModel/Display/DefaultDataModelDisplayView.axaml.cs +++ b/src/Avalonia/Artemis.UI.Shared/DefaultTypes/DataModel/Display/DefaultDataModelDisplayView.axaml.cs @@ -1,11 +1,16 @@ -using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; namespace Artemis.UI.Shared.DefaultTypes.DataModel.Display { - public partial class DefaultDataModelDisplayView : UserControl + /// + /// Represents a default data model display view. + /// + public class DefaultDataModelDisplayView : UserControl { + /// + /// Creates a new instance of the class. + /// public DefaultDataModelDisplayView() { InitializeComponent(); @@ -16,4 +21,4 @@ namespace Artemis.UI.Shared.DefaultTypes.DataModel.Display AvaloniaXamlLoader.Load(this); } } -} +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI.Shared/DefaultTypes/DataModel/Display/DefaultDataModelDisplayViewModel.cs b/src/Avalonia/Artemis.UI.Shared/DefaultTypes/DataModel/Display/DefaultDataModelDisplayViewModel.cs index 86d1f9fdf..80578c565 100644 --- a/src/Avalonia/Artemis.UI.Shared/DefaultTypes/DataModel/Display/DefaultDataModelDisplayViewModel.cs +++ b/src/Avalonia/Artemis.UI.Shared/DefaultTypes/DataModel/Display/DefaultDataModelDisplayViewModel.cs @@ -22,6 +22,7 @@ namespace Artemis.UI.Shared.DefaultTypes.DataModel.Display ReferenceLoopHandling = ReferenceLoopHandling.Ignore, PreserveReferencesHandling = PreserveReferencesHandling.None }; + _display = "null"; } public string Display diff --git a/src/Avalonia/Artemis.UI.Shared/Services/Builders/ContentDialogBuilder.cs b/src/Avalonia/Artemis.UI.Shared/Services/Builders/ContentDialogBuilder.cs index b6c65f276..b0a5c49d1 100644 --- a/src/Avalonia/Artemis.UI.Shared/Services/Builders/ContentDialogBuilder.cs +++ b/src/Avalonia/Artemis.UI.Shared/Services/Builders/ContentDialogBuilder.cs @@ -10,6 +10,9 @@ using ReactiveUI; namespace Artemis.UI.Shared.Services.Builders { + /// + /// Represents a builder that can be used to create Fluent UI dialogs. + /// public class ContentDialogBuilder { private readonly ContentDialog _contentDialog; @@ -27,24 +30,44 @@ namespace Artemis.UI.Shared.Services.Builders }; } + /// + /// Changes the title of the dialog. + /// + /// The new title. + /// The builder that can be used to further build the dialog. public ContentDialogBuilder WithTitle(string? title) { _contentDialog.Title = title; return this; } + /// + /// Changes the content of the dialog. + /// + /// The new content. + /// The builder that can be used to further build the dialog. public ContentDialogBuilder WithContent(string? content) { _contentDialog.Content = content; return this; } + /// + /// Changes the default button of the dialog that is pressed on enter. + /// + /// The default button. + /// The builder that can be used to further build the dialog. public ContentDialogBuilder WithDefaultButton(ContentDialogButton defaultButton) { - _contentDialog.DefaultButton = defaultButton; + _contentDialog.DefaultButton = (FluentAvalonia.UI.Controls.ContentDialogButton) defaultButton; return this; } + /// + /// Changes the primary button of the dialog. + /// + /// An action to configure the button. + /// The builder that can be used to further build the dialog. public ContentDialogBuilder HavingPrimaryButton(Action configure) { ContentDialogButtonBuilder builder = new(); @@ -65,6 +88,11 @@ namespace Artemis.UI.Shared.Services.Builders return this; } + /// + /// Changes the secondary button of the dialog. + /// + /// An action to configure the button. + /// The builder that can be used to further build the dialog. public ContentDialogBuilder HavingSecondaryButton(Action configure) { ContentDialogButtonBuilder builder = new(); @@ -85,12 +113,24 @@ namespace Artemis.UI.Shared.Services.Builders return this; } + /// + /// Changes the text of the close button of the dialog. + /// + /// The new text. + /// The builder that can be used to further build the dialog. public ContentDialogBuilder WithCloseButtonText(string? text) { _contentDialog.CloseButtonText = text; return this; } + /// + /// Changes the view model of the content dialog, hosting it inside the dialog. + /// + /// The type of the view model to host. + /// The resulting view model. + /// Optional parameters to pass to the constructor of the view model, case and order sensitive. + /// The builder that can be used to further build the dialog. public ContentDialogBuilder WithViewModel(out T viewModel, params (string name, object? value)[] parameters) where T : ContentDialogViewModelBase { IParameter[] paramsArray = parameters.Select(kv => new ConstructorArgument(kv.name, kv.value)).Cast().ToArray(); @@ -102,6 +142,11 @@ namespace Artemis.UI.Shared.Services.Builders return this; } + /// + /// Asynchronously shows the content dialog. + /// + /// A task containing the result of the content dialog. + /// Thrown when the parent window does not contain a panel at its root. public async Task ShowAsync() { if (_parent.Content is not Panel panel) @@ -111,9 +156,9 @@ namespace Artemis.UI.Shared.Services.Builders { panel.Children.Add(_contentDialog); ContentDialogResult result = await _contentDialog.ShowAsync(); - + // Take the dialog away from the VM in case it's going to try to hide it again or whatever... - if (_viewModel != null) + if (_viewModel != null) _viewModel.ContentDialog = null; return result; @@ -125,6 +170,35 @@ namespace Artemis.UI.Shared.Services.Builders } } + /// + /// Represents a content dialog button. + /// + public enum ContentDialogButton + { + /// + /// No button. + /// + None, + + /// + /// The primary button. + /// + Primary, + + /// + /// The secondary button. + /// + Secondary, + + /// + /// The close button. + /// + Close, + } + + /// + /// Represents a builder that can be used to create buttons inside content dialogs. + /// public class ContentDialogButtonBuilder { internal ContentDialogButtonBuilder() @@ -133,20 +207,48 @@ namespace Artemis.UI.Shared.Services.Builders internal string? Text { get; set; } internal ICommand? Command { get; set; } + internal Action? Action { get; set; } internal object? CommandParameter { get; set; } + /// + /// Changes text message of the button. + /// + /// The new text. + /// The notification builder that can be used to further build the button. public ContentDialogButtonBuilder WithText(string? text) { Text = text; return this; } + /// + /// Changes action that is called when the button is clicked. + /// + /// The action to call when the button is clicked. + /// The builder that can be used to further build the button. + public ContentDialogButtonBuilder WithAction(Action action) + { + Action = action; + Command = ReactiveCommand.Create(() => Action()); + return this; + } + + /// + /// Changes command that is called when the button is clicked. + /// + /// The command to call when the button is clicked. + /// The builder that can be used to further build the button. public ContentDialogButtonBuilder WithCommand(ICommand? command) { Command = command; return this; } + /// + /// Changes parameter of the command that is called when the button is clicked. + /// + /// The parameter of the command to call when the button is clicked. + /// The builder that can be used to further build the button. public ContentDialogButtonBuilder WithCommandParameter(object? commandParameter) { CommandParameter = commandParameter; diff --git a/src/Avalonia/Artemis.UI.Shared/Services/Builders/NotificationBuilder.cs b/src/Avalonia/Artemis.UI.Shared/Services/Builders/NotificationBuilder.cs index 57167bb6b..dab6ab14e 100644 --- a/src/Avalonia/Artemis.UI.Shared/Services/Builders/NotificationBuilder.cs +++ b/src/Avalonia/Artemis.UI.Shared/Services/Builders/NotificationBuilder.cs @@ -101,7 +101,7 @@ namespace Artemis.UI.Shared.Services.Builders } /// - /// Changes the action button of the dialog. + /// Changes the action button of the notification. /// /// An action to configure the button. /// The notification builder that can be used to further build the notification. @@ -154,6 +154,7 @@ namespace Artemis.UI.Shared.Services.Builders private Action? _action; private ICommand? _command; private string _text = "Text"; + private object? _commandParameter; /// /// Changes text message of the button. @@ -170,7 +171,7 @@ namespace Artemis.UI.Shared.Services.Builders /// Changes action that is called when the button is clicked. /// /// The action to call when the button is clicked. - /// The notification builder that can be used to further build the button. + /// The builder that can be used to further build the button. public NotificationButtonBuilder WithAction(Action action) { _command = null; @@ -182,7 +183,7 @@ namespace Artemis.UI.Shared.Services.Builders /// Changes command that is called when the button is clicked. /// /// The command to call when the button is clicked. - /// The notification builder that can be used to further build the button. + /// The builder that can be used to further build the button. public NotificationButtonBuilder WithCommand(ICommand command) { _action = null; @@ -190,21 +191,50 @@ namespace Artemis.UI.Shared.Services.Builders return this; } + /// + /// Changes parameter of the command that is called when the button is clicked. + /// + /// The parameter of the command to call when the button is clicked. + /// The builder that can be used to further build the button. + public NotificationButtonBuilder WithCommandParameter(object? commandParameter) + { + _commandParameter = commandParameter; + return this; + } + internal IControl Build() { if (_action != null) return new Button {Content = _text, Command = ReactiveCommand.Create(() => _action())}; if (_command != null) - return new Button {Content = _text, Command = _command}; + return new Button {Content = _text, Command = _command, CommandParameter = _commandParameter}; return new Button {Content = _text}; } } + /// + /// Represents a severity of a notification. + /// public enum NotificationSeverity { + /// + /// A severity for informational messages. + /// Informational, + + /// + /// A severity for success messages. + /// Success, + + /// + /// A severity for warning messages. + /// Warning, + + /// + /// A severity for error messages. + /// Error } } \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI.Shared/Services/Builders/OpenFileDialogBuilder.cs b/src/Avalonia/Artemis.UI.Shared/Services/Builders/OpenFileDialogBuilder.cs index af6d98ae3..a89fdedb8 100644 --- a/src/Avalonia/Artemis.UI.Shared/Services/Builders/OpenFileDialogBuilder.cs +++ b/src/Avalonia/Artemis.UI.Shared/Services/Builders/OpenFileDialogBuilder.cs @@ -12,6 +12,10 @@ namespace Artemis.UI.Shared.Services.Builders private readonly OpenFileDialog _openFileDialog; private readonly Window _parent; + /// + /// Creates a new instance of the class. + /// + /// The parent window that will host the dialog. public OpenFileDialogBuilder(Window parent) { _parent = parent; @@ -67,7 +71,7 @@ namespace Artemis.UI.Shared.Services.Builders } /// - /// Shows the file dialog + /// Asynchronously shows the file dialog. /// /// /// A task that on completion returns an array containing the full path to the selected diff --git a/src/Avalonia/Artemis.UI.Shared/Services/Builders/SaveFileDialogBuilder.cs b/src/Avalonia/Artemis.UI.Shared/Services/Builders/SaveFileDialogBuilder.cs index e9bc452c1..71d48c440 100644 --- a/src/Avalonia/Artemis.UI.Shared/Services/Builders/SaveFileDialogBuilder.cs +++ b/src/Avalonia/Artemis.UI.Shared/Services/Builders/SaveFileDialogBuilder.cs @@ -12,6 +12,10 @@ namespace Artemis.UI.Shared.Services.Builders private readonly Window _parent; private readonly SaveFileDialog _saveFileDialog; + /// + /// Creates a new instance of the class. + /// + /// The parent window that will host the notification. public SaveFileDialogBuilder(Window parent) { _parent = parent; @@ -67,7 +71,7 @@ namespace Artemis.UI.Shared.Services.Builders } /// - /// Shows the save file dialog. + /// Asynchronously shows the save file dialog. /// /// /// A task that on completion contains the full path of the save location, or null if the diff --git a/src/Avalonia/Artemis.UI.Shared/Services/Interfaces/INotificationService.cs b/src/Avalonia/Artemis.UI.Shared/Services/Interfaces/INotificationService.cs index 580e1d99d..3ad5418dd 100644 --- a/src/Avalonia/Artemis.UI.Shared/Services/Interfaces/INotificationService.cs +++ b/src/Avalonia/Artemis.UI.Shared/Services/Interfaces/INotificationService.cs @@ -2,8 +2,15 @@ namespace Artemis.UI.Shared.Services.Interfaces { + /// + /// A service that can be used to create notifications in either the application or on the desktop. + /// public interface INotificationService : IArtemisSharedUIService { + /// + /// Creates an in-app notification using a builder. + /// + /// A builder used to configure and show the notification. NotificationBuilder CreateNotification(); } } \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI.Shared/Services/Interfaces/IWindowService.cs b/src/Avalonia/Artemis.UI.Shared/Services/Interfaces/IWindowService.cs index 9dcf4ce8a..97af484ee 100644 --- a/src/Avalonia/Artemis.UI.Shared/Services/Interfaces/IWindowService.cs +++ b/src/Avalonia/Artemis.UI.Shared/Services/Interfaces/IWindowService.cs @@ -5,30 +5,34 @@ using Avalonia.Controls; namespace Artemis.UI.Shared.Services.Interfaces { + /// + /// A service that can be used to show windows and dialogs. + /// public interface IWindowService : IArtemisSharedUIService { /// - /// Creates a view model instance of type and shows its corresponding View as a window + /// Creates a view model instance of type and shows its corresponding View as a + /// window /// /// The type of view model to create /// The created view model TViewModel ShowWindow(params (string name, object value)[] parameters); /// - /// Given a ViewModel, show its corresponding View as a window + /// Given a ViewModel, show its corresponding View as a window /// /// ViewModel to show the View for void ShowWindow(object viewModel); /// - /// Shows a dialog displaying the given exception + /// Shows a dialog displaying the given exception /// /// The title of the dialog /// The exception to display void ShowExceptionDialog(string title, Exception exception); /// - /// Given an existing ViewModel, show its corresponding View as a Dialog + /// Given an existing ViewModel, show its corresponding View as a Dialog /// /// The return type /// ViewModel to show the View for @@ -36,7 +40,8 @@ namespace Artemis.UI.Shared.Services.Interfaces Task ShowDialogAsync(DialogViewModelBase viewModel); /// - /// Creates a view model instance of type and shows its corresponding View as a Dialog + /// Creates a view model instance of type and shows its corresponding View as a + /// Dialog /// /// The view model type /// The return type @@ -44,37 +49,40 @@ namespace Artemis.UI.Shared.Services.Interfaces Task ShowDialogAsync(params (string name, object? value)[] parameters) where TViewModel : DialogViewModelBase; /// - /// Shows a content dialog asking the user to confirm an action + /// Shows a content dialog asking the user to confirm an action /// /// The title of the dialog /// The message of the dialog /// The text of the confirm button - /// The text of the cancel button, if the cancel button will not be shown - /// A task containing the result of the dialog, if confirmed; otherwise + /// The text of the cancel button, if the cancel button will not be shown + /// + /// A task containing the result of the dialog, if confirmed; otherwise + /// + /// Task ShowConfirmContentDialog(string title, string message, string confirm = "Confirm", string? cancel = "Cancel"); /// - /// Creates an open file dialog, use the fluent API to configure it + /// Creates an open file dialog, use the fluent API to configure it /// /// The builder that can be used to configure the dialog OpenFileDialogBuilder CreateOpenFileDialog(); /// - /// Creates a save file dialog, use the fluent API to configure it + /// Creates a save file dialog, use the fluent API to configure it /// /// The builder that can be used to configure the dialog SaveFileDialogBuilder CreateSaveFileDialog(); /// - /// Creates a content dialog, use the fluent API to configure it + /// Creates a content dialog, use the fluent API to configure it /// /// The builder that can be used to configure the dialog ContentDialogBuilder CreateContentDialog(); /// - /// Gets the current window of the application + /// Gets the current window of the application /// /// The current window of the application - Window GetCurrentWindow(); + Window? GetCurrentWindow(); } } \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI.Shared/Services/MainWindowService/IMainWindowService.cs b/src/Avalonia/Artemis.UI.Shared/Services/MainWindowService/IMainWindowService.cs index 8a42d73a9..1022f39cb 100644 --- a/src/Avalonia/Artemis.UI.Shared/Services/MainWindowService/IMainWindowService.cs +++ b/src/Avalonia/Artemis.UI.Shared/Services/MainWindowService/IMainWindowService.cs @@ -3,6 +3,9 @@ using Artemis.UI.Shared.Services.Interfaces; namespace Artemis.UI.Shared.Services.MainWindowService { + /// + /// A service that can be used to manage the state of the main window. + /// public interface IMainWindowService : IArtemisSharedUIService { /// diff --git a/src/Avalonia/Artemis.UI.Shared/Services/NotificationService.cs b/src/Avalonia/Artemis.UI.Shared/Services/NotificationService.cs index 224fbf0cb..ed9408593 100644 --- a/src/Avalonia/Artemis.UI.Shared/Services/NotificationService.cs +++ b/src/Avalonia/Artemis.UI.Shared/Services/NotificationService.cs @@ -1,9 +1,10 @@ using Artemis.UI.Shared.Services.Builders; using Artemis.UI.Shared.Services.Interfaces; +using Avalonia.Controls; namespace Artemis.UI.Shared.Services { - public class NotificationService : INotificationService + internal class NotificationService : INotificationService { private readonly IWindowService _windowService; @@ -14,7 +15,11 @@ namespace Artemis.UI.Shared.Services public NotificationBuilder CreateNotification() { - return new NotificationBuilder(_windowService.GetCurrentWindow()); + Window? currentWindow = _windowService.GetCurrentWindow(); + if (currentWindow == null) + throw new ArtemisSharedUIException("Can't show an in-app notification without any windows being shown."); + + return new NotificationBuilder(currentWindow); } } } \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI.Shared/Services/WindowService/ExceptionDialogViewModel.cs b/src/Avalonia/Artemis.UI.Shared/Services/WindowService/ExceptionDialogViewModel.cs index 093c9e53e..9aa31727b 100644 --- a/src/Avalonia/Artemis.UI.Shared/Services/WindowService/ExceptionDialogViewModel.cs +++ b/src/Avalonia/Artemis.UI.Shared/Services/WindowService/ExceptionDialogViewModel.cs @@ -24,6 +24,9 @@ namespace Artemis.UI.Shared.Services public async Task CopyException() { + if (Application.Current?.Clipboard == null) + return; + await Application.Current.Clipboard.SetTextAsync(Exception.ToString()); _notificationService.CreateNotification() .WithMessage("Copied stack trace to clipboard.") diff --git a/src/Avalonia/Artemis.UI.Shared/Services/WindowService/WindowService.cs b/src/Avalonia/Artemis.UI.Shared/Services/WindowService/WindowService.cs index 691eca3cf..7479b4b16 100644 --- a/src/Avalonia/Artemis.UI.Shared/Services/WindowService/WindowService.cs +++ b/src/Avalonia/Artemis.UI.Shared/Services/WindowService/WindowService.cs @@ -39,14 +39,10 @@ namespace Artemis.UI.Shared.Services Type? type = viewModel.GetType().Assembly.GetType(name); if (type == null) - { throw new ArtemisSharedUIException($"Failed to find a window named {name}."); - } if (!type.IsAssignableTo(typeof(Window))) - { throw new ArtemisSharedUIException($"Type {name} is not a window."); - } Window window = (Window) Activator.CreateInstance(type)!; window.DataContext = viewModel; @@ -77,20 +73,16 @@ namespace Artemis.UI.Shared.Services public async Task ShowDialogAsync(DialogViewModelBase viewModel) { - Window parent = GetCurrentWindow(); + Window? parent = GetCurrentWindow(); string name = viewModel.GetType().FullName!.Split('`')[0].Replace("ViewModel", "View"); Type? type = viewModel.GetType().Assembly.GetType(name); if (type == null) - { throw new ArtemisSharedUIException($"Failed to find a window named {name}."); - } if (!type.IsAssignableTo(typeof(Window))) - { throw new ArtemisSharedUIException($"Type {name} is not a window."); - } Window window = (Window) Activator.CreateInstance(type)!; window.DataContext = viewModel; @@ -122,25 +114,32 @@ namespace Artemis.UI.Shared.Services public ContentDialogBuilder CreateContentDialog() { - return new ContentDialogBuilder(_kernel, GetCurrentWindow()); + Window? currentWindow = GetCurrentWindow(); + if (currentWindow == null) + throw new ArtemisSharedUIException("Can't show a content dialog without any windows being shown."); + return new ContentDialogBuilder(_kernel, currentWindow); } public OpenFileDialogBuilder CreateOpenFileDialog() { - return new OpenFileDialogBuilder(GetCurrentWindow()); + Window? currentWindow = GetCurrentWindow(); + if (currentWindow == null) + throw new ArtemisSharedUIException("Can't show an open file dialog without any windows being shown."); + return new OpenFileDialogBuilder(currentWindow); } public SaveFileDialogBuilder CreateSaveFileDialog() { - return new SaveFileDialogBuilder(GetCurrentWindow()); + Window? currentWindow = GetCurrentWindow(); + if (currentWindow == null) + throw new ArtemisSharedUIException("Can't show a save file dialog without any windows being shown."); + return new SaveFileDialogBuilder(currentWindow); } public Window? GetCurrentWindow() { - if (Application.Current.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime classic) - { - throw new ArtemisSharedUIException("Can't show a dialog when application lifetime is not IClassicDesktopStyleApplicationLifetime."); - } + if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime classic) + throw new ArtemisSharedUIException("Find an open window when application lifetime is not IClassicDesktopStyleApplicationLifetime."); Window? parent = classic.Windows.FirstOrDefault(w => w.IsActive && w.ShowInTaskbar) ?? classic.MainWindow; return parent; diff --git a/src/Avalonia/Artemis.UI.Windows/ApplicationStateManager.cs b/src/Avalonia/Artemis.UI.Windows/ApplicationStateManager.cs index e12820631..aec129e73 100644 --- a/src/Avalonia/Artemis.UI.Windows/ApplicationStateManager.cs +++ b/src/Avalonia/Artemis.UI.Windows/ApplicationStateManager.cs @@ -34,7 +34,7 @@ namespace Artemis.UI.Windows Core.Utilities.RestartRequested += UtilitiesOnRestartRequested; // On Windows shutdown dispose the kernel just so device providers get a chance to clean up - if (Application.Current.ApplicationLifetime is IControlledApplicationLifetime controlledApplicationLifetime) + if (Application.Current?.ApplicationLifetime is IControlledApplicationLifetime controlledApplicationLifetime) { controlledApplicationLifetime.Exit += (_, _) => { @@ -159,7 +159,7 @@ namespace Artemis.UI.Windows } // Lets try a graceful shutdown, PowerShell will kill if needed - if (Application.Current.ApplicationLifetime is IControlledApplicationLifetime controlledApplicationLifetime) + if (Application.Current?.ApplicationLifetime is IControlledApplicationLifetime controlledApplicationLifetime) Dispatcher.UIThread.Post(() => controlledApplicationLifetime.Shutdown()); } @@ -168,7 +168,7 @@ namespace Artemis.UI.Windows // Use PowerShell to kill the process after 8 sec just in case RunForcedShutdownIfEnabled(); - if (Application.Current.ApplicationLifetime is IControlledApplicationLifetime controlledApplicationLifetime) + if (Application.Current?.ApplicationLifetime is IControlledApplicationLifetime controlledApplicationLifetime) Dispatcher.UIThread.Post(() => controlledApplicationLifetime.Shutdown()); } diff --git a/src/Avalonia/Artemis.UI.Windows/Artemis.UI.Windows.csproj b/src/Avalonia/Artemis.UI.Windows/Artemis.UI.Windows.csproj index 8197e5866..09a66cd75 100644 --- a/src/Avalonia/Artemis.UI.Windows/Artemis.UI.Windows.csproj +++ b/src/Avalonia/Artemis.UI.Windows/Artemis.UI.Windows.csproj @@ -3,6 +3,7 @@ WinExe net5.0-windows enable + x64 diff --git a/src/Avalonia/Artemis.UI.Windows/Providers/Input/WindowsInputProvider.cs b/src/Avalonia/Artemis.UI.Windows/Providers/Input/WindowsInputProvider.cs index ef4f81bd9..30ed82688 100644 --- a/src/Avalonia/Artemis.UI.Windows/Providers/Input/WindowsInputProvider.cs +++ b/src/Avalonia/Artemis.UI.Windows/Providers/Input/WindowsInputProvider.cs @@ -113,13 +113,13 @@ namespace Artemis.UI.Windows.Providers.Input if (key == KeyboardKey.LeftCtrl && keyboardData.Keyboard.ScanCode == 56) return; - string identifier = data.Device?.DevicePath; + string? identifier = data.Device?.DevicePath; // Let the core know there is an identifier so it can store new identifications if applicable if (identifier != null) OnIdentifierReceived(identifier, InputDeviceType.Keyboard); - ArtemisDevice device = null; + ArtemisDevice? device = null; if (identifier != null) try { @@ -180,9 +180,10 @@ namespace Artemis.UI.Windows.Providers.Input return; } - ArtemisDevice device = null; - string identifier = data.Device?.DevicePath; + ArtemisDevice? device = null; + string? identifier = data.Device?.DevicePath; if (identifier != null) + { try { device = _inputService.GetDeviceByIdentifier(this, identifier, InputDeviceType.Mouse); @@ -191,6 +192,7 @@ namespace Artemis.UI.Windows.Providers.Input { _logger.Warning(e, "Failed to retrieve input device by its identifier"); } + } // Debug.WriteLine($"Buttons: {mouseData.Mouse.Buttons}, Data: {mouseData.Mouse.ButtonData}, Flags: {mouseData.Mouse.Flags}, XY: {mouseData.Mouse.LastX},{mouseData.Mouse.LastY}"); diff --git a/src/Avalonia/Artemis.UI.Windows/Utilities/ProcessUtilities.cs b/src/Avalonia/Artemis.UI.Windows/Utilities/ProcessUtilities.cs index fc61a664c..7bf9ead89 100644 --- a/src/Avalonia/Artemis.UI.Windows/Utilities/ProcessUtilities.cs +++ b/src/Avalonia/Artemis.UI.Windows/Utilities/ProcessUtilities.cs @@ -91,7 +91,7 @@ namespace Artemis.UI.Windows.Utilities } PROCESS_INFORMATION pi = new(); - if (!CreateProcessWithTokenW(hPrimaryToken, 0, fileName, $"\"{fileName}\" {arguments}", 0, IntPtr.Zero, Path.GetDirectoryName(fileName), ref si, out pi)) + if (!CreateProcessWithTokenW(hPrimaryToken, 0, fileName, $"\"{fileName}\" {arguments}", 0, IntPtr.Zero, Path.GetDirectoryName(fileName)!, ref si, out pi)) { // Get the last error and display it. int error = Marshal.GetLastWin32Error(); @@ -203,7 +203,7 @@ namespace Artemis.UI.Windows.Utilities private static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok); [DllImport("advapi32.dll", SetLastError = true)] - private static extern bool LookupPrivilegeValue(string host, string name, ref LUID pluid); + private static extern bool LookupPrivilegeValue(string? host, string name, ref LUID pluid); [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)] private static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, ref TOKEN_PRIVILEGES newst, int len, IntPtr prev, IntPtr relen); diff --git a/src/Avalonia/Artemis.UI/Converters/ColorToSolidColorBrushConverter.cs b/src/Avalonia/Artemis.UI/Converters/ColorToSolidColorBrushConverter.cs index 5e764ef9e..7bf4a26e7 100644 --- a/src/Avalonia/Artemis.UI/Converters/ColorToSolidColorBrushConverter.cs +++ b/src/Avalonia/Artemis.UI/Converters/ColorToSolidColorBrushConverter.cs @@ -12,7 +12,7 @@ namespace Artemis.UI.Converters public class ColorToSolidColorBrushConverter : IValueConverter { /// - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { return new SolidColorBrush(!(value is RGBColor color) ? new Color(0, 0, 0, 0) @@ -20,7 +20,7 @@ namespace Artemis.UI.Converters } /// - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { return !(value is SolidColorBrush brush) ? RGBColor.Transparent diff --git a/src/Avalonia/Artemis.UI/Converters/EnumToCollectionConverter.cs b/src/Avalonia/Artemis.UI/Converters/EnumToCollectionConverter.cs deleted file mode 100644 index 4d64bc484..000000000 --- a/src/Avalonia/Artemis.UI/Converters/EnumToCollectionConverter.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Globalization; -using System.Linq; -using Avalonia.Data.Converters; -using Avalonia.Markup.Xaml; - -namespace Artemis.UI.Converters -{ - public class EnumToCollectionConverter : MarkupExtension, IValueConverter - { - public override object ProvideValue(IServiceProvider serviceProvider) - { - return this; - } - - private static string Description(Enum value) - { - object[] attributes = value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false); - if (attributes.Any()) - return (attributes.First() as DescriptionAttribute)?.Description; - - // If no description is found, the least we can do is replace underscores with spaces - TextInfo ti = CultureInfo.CurrentCulture.TextInfo; - return ti.ToTitleCase(ti.ToLower(value.ToString().Replace("_", " "))); - } - - private static IEnumerable> GetAllValuesAndDescriptions(Type t) - { - if (!t.IsEnum) - throw new ArgumentException($"{nameof(t)} must be an enum type"); - - return Enum.GetValues(t).Cast().Select(e => new Tuple(e, Description(e))).ToList(); - } - - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - return GetAllValuesAndDescriptions(value.GetType()); - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - return null; - } - } -} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Converters/LedIdToStringConverter.cs b/src/Avalonia/Artemis.UI/Converters/LedIdToStringConverter.cs index 2160662ff..c8bdc8cd2 100644 --- a/src/Avalonia/Artemis.UI/Converters/LedIdToStringConverter.cs +++ b/src/Avalonia/Artemis.UI/Converters/LedIdToStringConverter.cs @@ -10,15 +10,15 @@ namespace Artemis.UI.Converters #region Implementation of IValueConverter /// - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { return value?.ToString(); } /// - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { - if (Enum.TryParse(typeof(LedId), value?.ToString(), true, out object parsedLedId)) + if (Enum.TryParse(typeof(LedId), value?.ToString(), true, out object? parsedLedId)) return parsedLedId; return LedId.Unknown1; } diff --git a/src/Avalonia/Artemis.UI/Converters/NormalizedPercentageConverter.cs b/src/Avalonia/Artemis.UI/Converters/NormalizedPercentageConverter.cs index 8a653a026..7c5673400 100644 --- a/src/Avalonia/Artemis.UI/Converters/NormalizedPercentageConverter.cs +++ b/src/Avalonia/Artemis.UI/Converters/NormalizedPercentageConverter.cs @@ -8,7 +8,7 @@ namespace Artemis.UI.Converters { #region IValueConverter Members - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { if (value is double number) return number * 100.0; @@ -16,7 +16,7 @@ namespace Artemis.UI.Converters return value; } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { if (value is double number) return number / 100.0; diff --git a/src/Avalonia/Artemis.UI/Converters/UriToFileNameConverter.cs b/src/Avalonia/Artemis.UI/Converters/UriToFileNameConverter.cs index b03483174..ed9e94cb9 100644 --- a/src/Avalonia/Artemis.UI/Converters/UriToFileNameConverter.cs +++ b/src/Avalonia/Artemis.UI/Converters/UriToFileNameConverter.cs @@ -7,14 +7,14 @@ namespace Artemis.UI.Converters { public class UriToFileNameConverter : IValueConverter { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { if (value is Uri uri && uri.IsFile) return Path.GetFileName(uri.LocalPath); return null; } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { return value; } diff --git a/src/Avalonia/Artemis.UI/Converters/ValuesAdditionConverter.cs b/src/Avalonia/Artemis.UI/Converters/ValuesAdditionConverter.cs index 529704fe0..7787287ea 100644 --- a/src/Avalonia/Artemis.UI/Converters/ValuesAdditionConverter.cs +++ b/src/Avalonia/Artemis.UI/Converters/ValuesAdditionConverter.cs @@ -9,8 +9,10 @@ namespace Artemis.UI.Converters public class ValuesAdditionConverter : IMultiValueConverter { /// - public object Convert(IList values, Type targetType, object parameter, CultureInfo culture) + public object Convert(IList? values, Type targetType, object? parameter, CultureInfo culture) { + if (values == null) + return 0.0; return values.Where(v => v is double).Cast().Sum(); } } diff --git a/src/Avalonia/Artemis.UI/Screens/Debugger/Tabs/Render/RenderDebugViewModel.cs b/src/Avalonia/Artemis.UI/Screens/Debugger/Tabs/Render/RenderDebugViewModel.cs index a86e5f53c..6729680c5 100644 --- a/src/Avalonia/Artemis.UI/Screens/Debugger/Tabs/Render/RenderDebugViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/Debugger/Tabs/Render/RenderDebugViewModel.cs @@ -16,7 +16,7 @@ namespace Artemis.UI.Screens.Debugger.Render private Bitmap? _currentFrame; private string? _frameTargetPath; - private string _renderer; + private string? _renderer; private int _renderHeight; private int _renderWidth; @@ -56,7 +56,7 @@ namespace Artemis.UI.Screens.Debugger.Render set => this.RaiseAndSetIfChanged(ref _renderHeight, value); } - public string Renderer + public string? Renderer { get => _renderer; set => this.RaiseAndSetIfChanged(ref _renderer, value); diff --git a/src/Avalonia/Artemis.UI/Screens/Device/Tabs/DeviceInfoTabViewModel.cs b/src/Avalonia/Artemis.UI/Screens/Device/Tabs/DeviceInfoTabViewModel.cs index 3005f651e..f56517716 100644 --- a/src/Avalonia/Artemis.UI/Screens/Device/Tabs/DeviceInfoTabViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/Device/Tabs/DeviceInfoTabViewModel.cs @@ -28,6 +28,9 @@ namespace Artemis.UI.Screens.Device public async Task CopyToClipboard(string content) { + if (Application.Current?.Clipboard == null) + return; + await Application.Current.Clipboard.SetTextAsync(content); _notificationService.CreateNotification().WithMessage("Copied path to clipboard.").Show(); } diff --git a/src/Avalonia/Artemis.UI/Screens/Device/Tabs/InputMappingsTabViewModel.cs b/src/Avalonia/Artemis.UI/Screens/Device/Tabs/InputMappingsTabViewModel.cs index 59e5ffc5f..6a174e615 100644 --- a/src/Avalonia/Artemis.UI/Screens/Device/Tabs/InputMappingsTabViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/Device/Tabs/InputMappingsTabViewModel.cs @@ -16,7 +16,7 @@ namespace Artemis.UI.Screens.Device private readonly IRgbService _rgbService; private readonly IInputService _inputService; private readonly ObservableCollection _selectedLeds; - private ArtemisLed _selectedLed; + private ArtemisLed? _selectedLed; public InputMappingsTabViewModel(ArtemisDevice device, ObservableCollection selectedLeds, IRgbService rgbService, IInputService inputService) { @@ -34,7 +34,7 @@ namespace Artemis.UI.Screens.Device public ArtemisDevice Device { get; } - public ArtemisLed SelectedLed + public ArtemisLed? SelectedLed { get => _selectedLed; set => this.RaiseAndSetIfChanged(ref _selectedLed, value); @@ -57,7 +57,7 @@ namespace Artemis.UI.Screens.Device bool foundLedId = InputKeyUtilities.KeyboardKeyLedIdMap.TryGetValue(e.Key, out LedId ledId); if (!foundLedId) return; - ArtemisLed artemisLed = Device.GetLed(ledId, false); + ArtemisLed? artemisLed = Device.GetLed(ledId, false); if (artemisLed == null) return; diff --git a/src/Avalonia/Artemis.UI/Screens/Root/RootViewModel.cs b/src/Avalonia/Artemis.UI/Screens/Root/RootViewModel.cs index 52f22ac97..72e4f7674 100644 --- a/src/Avalonia/Artemis.UI/Screens/Root/RootViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/Root/RootViewModel.cs @@ -53,7 +53,7 @@ namespace Artemis.UI.Screens.Root _assetLoader = assetLoader; _defaultTitleBarViewModel = defaultTitleBarViewModel; _sidebarVmFactory = sidebarVmFactory; - _lifeTime = (IClassicDesktopStyleApplicationLifetime) Application.Current.ApplicationLifetime; + _lifeTime = (IClassicDesktopStyleApplicationLifetime) Application.Current!.ApplicationLifetime!; coreService.StartupArguments = _lifeTime.Args.ToList(); mainWindowService.ConfigureMainWindowProvider(this); @@ -127,15 +127,15 @@ namespace Artemis.UI.Screens.Root Icon = new WindowIcon(_assetLoader.Open(new Uri("avares://Artemis.UI/Assets/Images/Logo/bow.ico"))), Command = ReactiveCommand.Create(OpenMainWindow) }; - _trayIcon.Menu = (NativeMenu?) Application.Current.FindResource("TrayIconMenu"); + _trayIcon.Menu = (NativeMenu?) Application.Current!.FindResource("TrayIconMenu"); _trayIcons = new TrayIcons {_trayIcon}; - TrayIcon.SetIcons(Application.Current, _trayIcons); + TrayIcon.SetIcons(Application.Current!, _trayIcons); } private void HideTrayIcon() { _trayIcon?.Dispose(); - TrayIcon.SetIcons(Application.Current, null!); + TrayIcon.SetIcons(Application.Current!, null!); _trayIcon = null; _trayIcons = null; diff --git a/src/Avalonia/Artemis.UI/Screens/Settings/Tabs/PluginsTabViewModel.cs b/src/Avalonia/Artemis.UI/Screens/Settings/Tabs/PluginsTabViewModel.cs index b722f835d..2777a26e5 100644 --- a/src/Avalonia/Artemis.UI/Screens/Settings/Tabs/PluginsTabViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/Settings/Tabs/PluginsTabViewModel.cs @@ -69,7 +69,7 @@ namespace Artemis.UI.Screens.Settings SearchPluginInput = plugin.Info.Name; // Enable it via the VM to enable the prerequisite dialog - PluginSettingsViewModel pluginViewModel = Plugins.FirstOrDefault(i => i.Plugin == plugin); + PluginSettingsViewModel? pluginViewModel = Plugins.FirstOrDefault(i => i.Plugin == plugin); if (pluginViewModel is {IsEnabled: false}) pluginViewModel.IsEnabled = true; diff --git a/src/Avalonia/Artemis.UI/Screens/Sidebar/Dialogs/ProfileConfigurationEditViewModel.cs b/src/Avalonia/Artemis.UI/Screens/Sidebar/Dialogs/ProfileConfigurationEditViewModel.cs index 74831634d..d1f66f1df 100644 --- a/src/Avalonia/Artemis.UI/Screens/Sidebar/Dialogs/ProfileConfigurationEditViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/Sidebar/Dialogs/ProfileConfigurationEditViewModel.cs @@ -22,18 +22,18 @@ namespace Artemis.UI.Screens.Sidebar private readonly ProfileCategory _profileCategory; private readonly IProfileService _profileService; private readonly IWindowService _windowService; - private ProfileConfigurationIconType _iconType; + private Hotkey? _disableHotkey; + private Hotkey? _enableHotkey; private ProfileConfigurationHotkeyMode _hotkeyMode; + private ProfileConfigurationIconType _iconType; private ObservableCollection? _materialIcons; private ProfileConfiguration _profileConfiguration; private string _profileName; private Bitmap? _selectedBitmapSource; + private string? _selectedIconPath; private ProfileIconViewModel? _selectedMaterialIcon; private ProfileModuleViewModel? _selectedModule; - private string? _selectedIconPath; private SvgImage? _selectedSvgSource; - private Hotkey? _enableHotkey; - private Hotkey? _disableHotkey; public ProfileConfigurationEditViewModel(ProfileCategory profileCategory, ProfileConfiguration? profileConfiguration, IWindowService windowService, IProfileService profileService, IPluginManagementService pluginManagementService) @@ -46,9 +46,9 @@ namespace Artemis.UI.Screens.Sidebar _iconType = _profileConfiguration.Icon.IconType; _hotkeyMode = _profileConfiguration.HotkeyMode; if (_profileConfiguration.EnableHotkey != null) - _enableHotkey = new Hotkey() {Key = _profileConfiguration.EnableHotkey.Key, Modifiers = profileConfiguration.EnableHotkey.Modifiers}; + _enableHotkey = new Hotkey {Key = _profileConfiguration.EnableHotkey.Key, Modifiers = _profileConfiguration.EnableHotkey.Modifiers}; if (_profileConfiguration.DisableHotkey != null) - _disableHotkey = new Hotkey() {Key = _profileConfiguration.DisableHotkey.Key, Modifiers = profileConfiguration.DisableHotkey.Modifiers}; + _disableHotkey = new Hotkey {Key = _profileConfiguration.DisableHotkey.Key, Modifiers = _profileConfiguration.DisableHotkey.Modifiers}; IsNew = profileConfiguration == null; DisplayName = IsNew ? "Artemis | Add profile" : "Artemis | Edit profile"; @@ -142,7 +142,7 @@ namespace Artemis.UI.Screens.Sidebar return; if (!await _windowService.ShowConfirmContentDialog("Delete profile", "Are you sure you want to permanently delete this profile?")) return; - + _profileService.RemoveProfileConfiguration(_profileConfiguration); Close(true); } @@ -210,9 +210,13 @@ namespace Artemis.UI.Screens.Sidebar } else if (_profileConfiguration.Icon.IconType == ProfileConfigurationIconType.SvgImage) { - SvgSource newSource = new(); - newSource.Load(_profileConfiguration.Icon.GetIconStream()); - SelectedSvgSource = new SvgImage {Source = newSource}; + Stream? iconStream = _profileConfiguration.Icon.GetIconStream(); + if (iconStream != null) + { + SvgSource newSource = new(); + newSource.Load(iconStream); + SelectedSvgSource = new SvgImage {Source = newSource}; + } } // Prepare the contents of the dropdown box, it should be virtualized so no need to wait with this @@ -231,7 +235,9 @@ namespace Artemis.UI.Screens.Sidebar private async Task SaveIcon() { if (IconType == ProfileConfigurationIconType.MaterialIcon && SelectedMaterialIcon != null) + { ProfileConfiguration.Icon.SetIconByName(SelectedMaterialIcon.Icon.ToString()); + } else if (_selectedIconPath != null) { await using FileStream fileStream = File.OpenRead(_selectedIconPath); diff --git a/src/Avalonia/Artemis.UI/Screens/Sidebar/SidebarCategoryViewModel.cs b/src/Avalonia/Artemis.UI/Screens/Sidebar/SidebarCategoryViewModel.cs index e3f57b4da..a18d9afb8 100644 --- a/src/Avalonia/Artemis.UI/Screens/Sidebar/SidebarCategoryViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/Sidebar/SidebarCategoryViewModel.cs @@ -6,23 +6,24 @@ using System.Threading.Tasks; using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Ninject.Factories; -using Artemis.UI.Services; using Artemis.UI.Services.ProfileEditor; using Artemis.UI.Shared; +using Artemis.UI.Shared.Services.Builders; using Artemis.UI.Shared.Services.Interfaces; -using FluentAvalonia.UI.Controls; using ReactiveUI; + namespace Artemis.UI.Screens.Sidebar { public class SidebarCategoryViewModel : ActivatableViewModelBase { - private readonly SidebarViewModel _sidebarViewModel; private readonly IProfileService _profileService; - private readonly IWindowService _windowService; + private readonly SidebarViewModel _sidebarViewModel; private readonly ISidebarVmFactory _vmFactory; + private readonly IWindowService _windowService; private SidebarProfileConfigurationViewModel? _selectedProfileConfiguration; - public SidebarCategoryViewModel(SidebarViewModel sidebarViewModel, ProfileCategory profileCategory, IProfileService profileService, IWindowService windowService, IProfileEditorService profileEditorService, ISidebarVmFactory vmFactory) + public SidebarCategoryViewModel(SidebarViewModel sidebarViewModel, ProfileCategory profileCategory, IProfileService profileService, IWindowService windowService, + IProfileEditorService profileEditorService, ISidebarVmFactory vmFactory) { _sidebarViewModel = sidebarViewModel; _profileService = profileService; @@ -86,7 +87,7 @@ namespace Artemis.UI.Screens.Sidebar { await _windowService.CreateContentDialog() .WithTitle("Edit category") - .WithViewModel(out var vm, ("category", ProfileCategory)) + .WithViewModel(out SidebarCategoryEditViewModel vm, ("category", ProfileCategory)) .HavingPrimaryButton(b => b.WithText("Confirm").WithCommand(vm.Confirm)) .HavingSecondaryButton(b => b.WithText("Delete").WithCommand(vm.Delete)) .WithCloseButtonText("Cancel") diff --git a/src/Avalonia/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs b/src/Avalonia/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs index cff43a064..96777e626 100644 --- a/src/Avalonia/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs @@ -2,7 +2,6 @@ using System.Collections.ObjectModel; using System.Linq; using System.Reactive.Disposables; -using System.Reactive.Linq; using System.Threading.Tasks; using Artemis.Core; using Artemis.Core.Services; @@ -12,11 +11,10 @@ using Artemis.UI.Screens.ProfileEditor; using Artemis.UI.Screens.Settings; using Artemis.UI.Screens.SurfaceEditor; using Artemis.UI.Screens.Workshop; -using Artemis.UI.Services; using Artemis.UI.Services.ProfileEditor; using Artemis.UI.Shared; +using Artemis.UI.Shared.Services.Builders; using Artemis.UI.Shared.Services.Interfaces; -using FluentAvalonia.UI.Controls; using Material.Icons; using Ninject; using ReactiveUI; @@ -27,11 +25,11 @@ namespace Artemis.UI.Screens.Sidebar public class SidebarViewModel : ActivatableViewModelBase { private readonly IScreen _hostScreen; + private readonly IProfileEditorService _profileEditorService; private readonly IProfileService _profileService; private readonly IRgbService _rgbService; private readonly ISidebarVmFactory _sidebarVmFactory; private readonly IWindowService _windowService; - private readonly IProfileEditorService _profileEditorService; private ArtemisDevice? _headerDevice; private SidebarScreenViewModel? _selectedSidebarScreen; @@ -111,7 +109,7 @@ namespace Artemis.UI.Screens.Sidebar { await _windowService.CreateContentDialog() .WithTitle("Add new category") - .WithViewModel(out var vm, ("category", null)) + .WithViewModel(out SidebarCategoryEditViewModel vm, ("category", null)) .HavingPrimaryButton(b => b.WithText("Confirm").WithCommand(vm.Confirm)) .WithCloseButtonText("Cancel") .WithDefaultButton(ContentDialogButton.Primary) diff --git a/src/Avalonia/Artemis.UI/Screens/SurfaceEditor/SurfaceDeviceViewModel.cs b/src/Avalonia/Artemis.UI/Screens/SurfaceEditor/SurfaceDeviceViewModel.cs index 1025fdf9b..2e45e3e97 100644 --- a/src/Avalonia/Artemis.UI/Screens/SurfaceEditor/SurfaceDeviceViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/SurfaceEditor/SurfaceDeviceViewModel.cs @@ -35,6 +35,7 @@ namespace Artemis.UI.Screens.SurfaceEditor _settingsService = settingsService; _deviceVmFactory = deviceVmFactory; _windowService = windowService; + _cursor = Cursor.Default; Device = device;