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

UI - Refactorings, project builds and runs again :P

This commit is contained in:
Robert 2021-11-08 18:18:23 +01:00
parent c178fc6cf8
commit 41171a5ade
48 changed files with 895 additions and 205 deletions

View File

@ -1,9 +1,15 @@
namespace Artemis.Core
using System;
namespace Artemis.Core
{
/// <summary>
/// Represents a configuration dialog for a <see cref="Plugin" />
/// </summary>
public interface IPluginConfigurationDialog
{
/// <summary>
/// The type of view model the tab contains
/// </summary>
Type Type { get; }
}
}

View File

@ -89,6 +89,28 @@ namespace Artemis.Core
}
}
/// <summary>
/// Occurs when the core has requested an application shutdown
/// </summary>
public static event EventHandler? ShutdownRequested;
/// <summary>
/// Occurs when the core has requested an application restart
/// </summary>
public static event EventHandler<RestartEventArgs>? RestartRequested;
/// <summary>
/// Opens the provided folder in the user's file explorer
/// </summary>
/// <param name="path">The full path of the folder to open</param>
public static void OpenFolder(string path)
{
if (OperatingSystem.IsWindows())
Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", path);
else
throw new PlatformNotSupportedException("Can't open folders yet on non-Windows systems Q.Q");
}
/// <summary>
/// Gets the current application location
/// </summary>
@ -103,6 +125,11 @@ namespace Artemis.Core
RestartRequested?.Invoke(null, e);
}
private static void OnShutdownRequested()
{
ShutdownRequested?.Invoke(null, EventArgs.Empty);
}
#region Scaling
internal static int RenderScaleMultiplier { get; set; } = 2;
@ -118,32 +145,13 @@ namespace Artemis.Core
return SKRectI.Create(roundX, roundY, roundWidth, roundHeight);
return SKRectI.Create(
roundX - (roundX % RenderScaleMultiplier),
roundY - (roundY % RenderScaleMultiplier),
roundWidth - (roundWidth % RenderScaleMultiplier),
roundHeight - (roundHeight % RenderScaleMultiplier)
roundX - roundX % RenderScaleMultiplier,
roundY - roundY % RenderScaleMultiplier,
roundWidth - roundWidth % RenderScaleMultiplier,
roundHeight - roundHeight % RenderScaleMultiplier
);
}
#endregion
#region Events
/// <summary>
/// Occurs when the core has requested an application shutdown
/// </summary>
public static event EventHandler? ShutdownRequested;
/// <summary>
/// Occurs when the core has requested an application restart
/// </summary>
public static event EventHandler<RestartEventArgs>? RestartRequested;
private static void OnShutdownRequested()
{
ShutdownRequested?.Invoke(null, EventArgs.Empty);
}
#endregion
}
}

View File

@ -1,2 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwindowservice/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@ -0,0 +1,491 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>Artemis.UI.Avalonia.Shared</name>
</assembly>
<members>
<member name="T:Artemis.UI.Avalonia.Shared.Controls.DeviceVisualizer">
<summary>
Visualizes an <see cref="T:Artemis.Core.ArtemisDevice" /> with optional per-LED colors
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Controls.DeviceVisualizer.#ctor">
<inheritdoc />
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Controls.DeviceVisualizer.Render(Avalonia.Media.DrawingContext)">
<inheritdoc />
</member>
<member name="E:Artemis.UI.Avalonia.Shared.Controls.DeviceVisualizer.LedClicked">
<summary>
Occurs when a LED of the device has been clicked
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Controls.DeviceVisualizer.OnLedClicked(Artemis.UI.Avalonia.Shared.Events.LedClickedEventArgs)">
<summary>
Invokes the <see cref="E:Artemis.UI.Avalonia.Shared.Controls.DeviceVisualizer.LedClicked" /> event
</summary>
<param name="e"></param>
</member>
<member name="F:Artemis.UI.Avalonia.Shared.Controls.DeviceVisualizer.DeviceProperty">
<summary>
Gets or sets the <see cref="T:Artemis.Core.ArtemisDevice" /> to display
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Controls.DeviceVisualizer.Device">
<summary>
Gets or sets the <see cref="T:Artemis.Core.ArtemisDevice" /> to display
</summary>
</member>
<member name="F:Artemis.UI.Avalonia.Shared.Controls.DeviceVisualizer.ShowColorsProperty">
<summary>
Gets or sets boolean indicating whether or not to show per-LED colors
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Controls.DeviceVisualizer.ShowColors">
<summary>
Gets or sets a boolean indicating whether or not to show per-LED colors
</summary>
</member>
<member name="F:Artemis.UI.Avalonia.Shared.Controls.DeviceVisualizer.HighlightedLedsProperty">
<summary>
Gets or sets a list of LEDs to highlight
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Controls.DeviceVisualizer.HighlightedLeds">
<summary>
Gets or sets a list of LEDs to highlight
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Controls.DeviceVisualizer.OnAttachedToLogicalTree(Avalonia.LogicalTree.LogicalTreeAttachmentEventArgs)">
<inheritdoc />
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Controls.DeviceVisualizer.OnDetachedFromLogicalTree(Avalonia.LogicalTree.LogicalTreeAttachmentEventArgs)">
<inheritdoc />
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Controls.DeviceVisualizer.MeasureOverride(Avalonia.Size)">
<inheritdoc />
</member>
<member name="F:Artemis.UI.Avalonia.Shared.Controls.EnumComboBox.ValueProperty">
<summary>
Gets or sets the currently selected value
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Controls.EnumComboBox.Value">
<summary>
Gets or sets the currently selected value
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Controls.EnumComboBox.OnAttachedToLogicalTree(Avalonia.LogicalTree.LogicalTreeAttachmentEventArgs)">
<inheritdoc />
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Controls.EnumComboBox.OnDetachedFromLogicalTree(Avalonia.LogicalTree.LogicalTreeAttachmentEventArgs)">
<inheritdoc />
</member>
<member name="F:Artemis.UI.Avalonia.Shared.Controls.ProfileConfigurationIcon.ConfigurationIconProperty">
<summary>
Gets or sets the <see cref="T:Artemis.Core.ProfileConfigurationIcon" /> to display
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Controls.ProfileConfigurationIcon.ConfigurationIcon">
<summary>
Gets or sets the <see cref="T:Artemis.Core.ProfileConfigurationIcon" /> to display
</summary>
</member>
<member name="T:Artemis.UI.Avalonia.Shared.Controls.SelectionRectangle">
<summary>
Visualizes an <see cref="T:Artemis.Core.ArtemisDevice" /> with optional per-LED colors
</summary>
</member>
<member name="F:Artemis.UI.Avalonia.Shared.Controls.SelectionRectangle.BackgroundProperty">
<summary>
Defines the <see cref="P:Artemis.UI.Avalonia.Shared.Controls.SelectionRectangle.Background" /> property.
</summary>
</member>
<member name="F:Artemis.UI.Avalonia.Shared.Controls.SelectionRectangle.BorderBrushProperty">
<summary>
Defines the <see cref="P:Artemis.UI.Avalonia.Shared.Controls.SelectionRectangle.BorderBrush" /> property.
</summary>
</member>
<member name="F:Artemis.UI.Avalonia.Shared.Controls.SelectionRectangle.BorderThicknessProperty">
<summary>
Defines the <see cref="P:Artemis.UI.Avalonia.Shared.Controls.SelectionRectangle.BorderBrush" /> property.
</summary>
</member>
<member name="F:Artemis.UI.Avalonia.Shared.Controls.SelectionRectangle.InputElementProperty">
<summary>
Defines the <see cref="M:Artemis.UI.Avalonia.Shared.Controls.SelectionRectangle.get_InputElement" /> property.
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Controls.SelectionRectangle.#ctor">
<inheritdoc />
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Controls.SelectionRectangle.Background">
<summary>
Gets or sets a brush used to paint the control's background.
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Controls.SelectionRectangle.BorderBrush">
<summary>
Gets or sets a brush used to paint the control's border
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Controls.SelectionRectangle.BorderThickness">
<summary>
Gets or sets the width of the control's border
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Controls.SelectionRectangle.OnAttachedToVisualTree(Avalonia.VisualTreeAttachmentEventArgs)">
<inheritdoc />
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Controls.SelectionRectangle.OnDetachedFromVisualTree(Avalonia.VisualTreeAttachmentEventArgs)">
<inheritdoc />
</member>
<member name="T:Artemis.UI.Avalonia.Shared.Converters.ColorToSKColorConverter">
<summary>
Converts <see cref="T:Avalonia.Media.Color" /> into <see cref="T:SkiaSharp.SKColor" />.
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Converters.ColorToSKColorConverter.Convert(System.Object,System.Type,System.Object,System.Globalization.CultureInfo)">
<inheritdoc />
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Converters.ColorToSKColorConverter.ConvertBack(System.Object,System.Type,System.Object,System.Globalization.CultureInfo)">
<inheritdoc />
</member>
<member name="T:Artemis.UI.Avalonia.Shared.Converters.SKColorToColorConverter">
<summary>
Converts <see cref="T:SkiaSharp.SKColor" /> into <see cref="T:Avalonia.Media.Color" />.
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Converters.SKColorToColorConverter.Convert(System.Object,System.Type,System.Object,System.Globalization.CultureInfo)">
<inheritdoc />
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Converters.SKColorToColorConverter.ConvertBack(System.Object,System.Type,System.Object,System.Globalization.CultureInfo)">
<inheritdoc />
</member>
<member name="T:Artemis.UI.Avalonia.Shared.Events.DataModelInputDynamicEventArgs">
<summary>
Provides data about selection events raised by <see cref="!:DataModelDynamicViewModel" />
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Events.DataModelInputDynamicEventArgs.DataModelPath">
<summary>
Gets the data model path that was selected
</summary>
</member>
<member name="T:Artemis.UI.Avalonia.Shared.Events.DataModelInputStaticEventArgs">
<summary>
Provides data about submit events raised by <see cref="!:DataModelStaticViewModel" />
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Events.DataModelInputStaticEventArgs.Value">
<summary>
The value that was submitted
</summary>
</member>
<member name="T:Artemis.UI.Avalonia.Shared.Events.LedClickedEventArgs">
<summary>
Provides data on LED click events raised by the device visualizer
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Events.LedClickedEventArgs.Device">
<summary>
The device that was clicked
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Events.LedClickedEventArgs.Led">
<summary>
The LED that was clicked
</summary>
</member>
<member name="T:Artemis.UI.Avalonia.Shared.Events.ProfileConfigurationEventArgs">
<summary>
Provides data on profile related events raised by the profile editor
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Events.ProfileConfigurationEventArgs.ProfileConfiguration">
<summary>
Gets the profile the event was raised for
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Events.ProfileConfigurationEventArgs.PreviousProfileConfiguration">
<summary>
If applicable, the previous active profile before the event was raised
</summary>
</member>
<member name="T:Artemis.UI.Avalonia.Shared.Events.RenderProfileElementEventArgs">
<summary>
Provides data on profile element related events raised by the profile editor
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Events.RenderProfileElementEventArgs.RenderProfileElement">
<summary>
Gets the profile element the event was raised for
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Events.RenderProfileElementEventArgs.PreviousRenderProfileElement">
<summary>
If applicable, the previous active profile element before the event was raised
</summary>
</member>
<member name="T:Artemis.UI.Avalonia.Shared.Exceptions.ArtemisSharedUIException">
<summary>
Represents errors that occur within the Artemis Shared UI library
</summary>
</member>
<member name="T:Artemis.UI.Avalonia.Shared.Ninject.SharedUIModule">
<summary>
The main <see cref="T:Ninject.Modules.NinjectModule" /> of the Artemis Shared UI toolkit that binds all services
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Ninject.SharedUIModule.Load">
<inheritdoc />
</member>
<member name="T:Artemis.UI.Avalonia.Shared.PluginConfigurationDialog`1">
<inheritdoc />
</member>
<member name="P:Artemis.UI.Avalonia.Shared.PluginConfigurationDialog`1.Type">
<inheritdoc />
</member>
<member name="T:Artemis.UI.Avalonia.Shared.PluginConfigurationDialog">
<summary>
Describes a configuration dialog for a specific plugin
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.PluginConfigurationDialog.Type">
<inheritdoc />
</member>
<member name="T:Artemis.UI.Avalonia.Shared.PluginConfigurationViewModel">
<summary>
Represents a view model for a plugin configuration window
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.PluginConfigurationViewModel.#ctor(Artemis.Core.Plugin)">
<summary>
Creates a new instance of the <see cref="T:Artemis.UI.Avalonia.Shared.PluginConfigurationViewModel" /> class
</summary>
<param name="plugin"></param>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.PluginConfigurationViewModel.Plugin">
<summary>
Gets the plugin this configuration view model is associated with
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.PluginConfigurationViewModel.Close">
<summary>
A command that closes the window
</summary>
</member>
<member name="T:Artemis.UI.Avalonia.Shared.Services.Builders.FileDialogFilterBuilder">
<summary>
Represents a builder that can create a <see cref="T:Avalonia.Controls.FileDialogFilter" />.
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Builders.FileDialogFilterBuilder.WithName(System.String)">
<summary>
Sets the name of the filter
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Builders.FileDialogFilterBuilder.WithExtension(System.String)">
<summary>
Adds the provided extension to the filter
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Builders.NotificationBuilder.HavingButton(System.Action{Artemis.UI.Avalonia.Shared.Services.Builders.NotificationButtonBuilder})">
<summary>
Add a filter to the dialog
</summary>
</member>
<member name="T:Artemis.UI.Avalonia.Shared.Services.Builders.OpenFileDialogBuilder">
<summary>
Represents a builder that can create a <see cref="T:Avalonia.Controls.OpenFileDialog" />.
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Builders.OpenFileDialogBuilder.WithAllowMultiple">
<summary>
Indicate that the user can select multiple files.
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Builders.OpenFileDialogBuilder.WithTitle(System.String)">
<summary>
Set the title of the dialog
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Builders.OpenFileDialogBuilder.WithDirectory(System.String)">
<summary>
Set the initial directory of the dialog
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Builders.OpenFileDialogBuilder.WithInitialFileName(System.String)">
<summary>
Set the initial file name of the dialog
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Builders.OpenFileDialogBuilder.HavingFilter(System.Action{Artemis.UI.Avalonia.Shared.Services.Builders.FileDialogFilterBuilder})">
<summary>
Add a filter to the dialog
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Builders.OpenFileDialogBuilder.ShowAsync">
<summary>
Shows the file dialog
</summary>
<returns>
A task that on completion returns an array containing the full path to the selected
files, or null if the dialog was canceled.
</returns>
</member>
<member name="T:Artemis.UI.Avalonia.Shared.Services.Builders.SaveFileDialogBuilder">
<summary>
Represents a builder that can create a <see cref="T:Avalonia.Controls.SaveFileDialog" />.
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Builders.SaveFileDialogBuilder.WithTitle(System.String)">
<summary>
Set the title of the dialog
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Builders.SaveFileDialogBuilder.WithDirectory(System.String)">
<summary>
Set the initial directory of the dialog
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Builders.SaveFileDialogBuilder.WithInitialFileName(System.String)">
<summary>
Set the initial file name of the dialog
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Builders.SaveFileDialogBuilder.WithDefaultExtension(System.String)">
<summary>
Set the default extension of the dialog
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Builders.SaveFileDialogBuilder.HavingFilter(System.Action{Artemis.UI.Avalonia.Shared.Services.Builders.FileDialogFilterBuilder})">
<summary>
Add a filter to the dialog
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Builders.SaveFileDialogBuilder.ShowAsync">
<summary>
Shows the save file dialog.
</summary>
<returns>
A task that on completion contains the full path of the save location, or null if the
dialog was canceled.
</returns>
</member>
<member name="T:Artemis.UI.Avalonia.Shared.Services.Interfaces.IArtemisSharedUIService">
<summary>
Represents a service provided by the Artemis Shared UI library
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Interfaces.IWindowService.ShowWindow``1(System.ValueTuple{System.String,System.Object}[])">
<summary>
Creates a view model instance of type <typeparamref name="TViewModel" /> and shows its corresponding View as a window
</summary>
<typeparam name="TViewModel">The type of view model to create</typeparam>
<returns>The created view model</returns>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Interfaces.IWindowService.ShowWindow(System.Object)">
<summary>
Given a ViewModel, show its corresponding View as a window
</summary>
<param name="viewModel">ViewModel to show the View for</param>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Interfaces.IWindowService.ShowExceptionDialog(System.String,System.Exception)">
<summary>
Shows a dialog displaying the given exception
</summary>
<param name="title">The title of the dialog</param>
<param name="exception">The exception to display</param>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Interfaces.IWindowService.ShowDialogAsync``1(Artemis.UI.Avalonia.Shared.DialogViewModelBase{``0})">
<summary>
Given an existing ViewModel, show its corresponding View as a Dialog
</summary>
<typeparam name="TResult">The return type</typeparam>
<param name="viewModel">ViewModel to show the View for</param>
<returns>A task containing the return value of type <typeparamref name="TResult" /></returns>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Interfaces.IWindowService.ShowDialogAsync``2(System.ValueTuple{System.String,System.Object}[])">
<summary>
Creates a view model instance of type <typeparamref name="TViewModel"/> and shows its corresponding View as a Dialog
</summary>
<typeparam name="TViewModel">The view model type</typeparam>
<typeparam name="TResult">The return type</typeparam>
<returns>A task containing the return value of type <typeparamref name="TResult" /></returns>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Interfaces.IWindowService.ShowConfirmContentDialog(System.String,System.String,System.String,System.String)">
<summary>
Shows a content dialog asking the user to confirm an action
</summary>
<param name="title">The title of the dialog</param>
<param name="message">The message of the dialog</param>
<param name="confirm">The text of the confirm button</param>
<param name="cancel">The text of the cancel button, if <see langword="null"/> the cancel button will not be shown</param>
<returns>A task containing the result of the dialog, <see langword="true"/> if confirmed; otherwise <see langword="false"/></returns>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Interfaces.IWindowService.CreateOpenFileDialog">
<summary>
Creates an open file dialog, use the fluent API to configure it
</summary>
<returns>The builder that can be used to configure the dialog</returns>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Interfaces.IWindowService.CreateSaveFileDialog">
<summary>
Creates a save file dialog, use the fluent API to configure it
</summary>
<returns>The builder that can be used to configure the dialog</returns>
</member>
<member name="T:Artemis.UI.Avalonia.Shared.ViewModelBase">
<summary>
Represents the base class for Artemis view models
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.ViewModelBase.DisplayName">
<summary>
Gets or sets the display name of the view model
</summary>
</member>
<member name="T:Artemis.UI.Avalonia.Shared.ActivatableViewModelBase">
<summary>
Represents the base class for Artemis view models that are interested in the activated event
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.ActivatableViewModelBase.#ctor">
<inheritdoc />
</member>
<member name="M:Artemis.UI.Avalonia.Shared.ActivatableViewModelBase.Dispose(System.Boolean)">
<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>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.ActivatableViewModelBase.Activator">
<inheritdoc />
</member>
<member name="M:Artemis.UI.Avalonia.Shared.ActivatableViewModelBase.Dispose">
<inheritdoc />
</member>
<member name="T:Artemis.UI.Avalonia.Shared.DialogViewModelBase`1">
<summary>
Represents the base class for Artemis view models used to drive dialogs
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.DialogViewModelBase`1.#ctor">
<inheritdoc />
</member>
<member name="P:Artemis.UI.Avalonia.Shared.DialogViewModelBase`1.Close">
<summary>
Closes the dialog with a given result
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.DialogViewModelBase`1.Cancel">
<summary>
Closes the dialog without a result
</summary>
</member>
</members>
</doc>

View File

@ -0,0 +1,21 @@
using System;
using Artemis.Core;
namespace Artemis.UI.Avalonia.Shared
{
/// <inheritdoc />
public class PluginConfigurationDialog<T> : PluginConfigurationDialog where T : PluginConfigurationViewModel
{
/// <inheritdoc />
public override Type Type => typeof(T);
}
/// <summary>
/// Describes a configuration dialog for a specific plugin
/// </summary>
public abstract class PluginConfigurationDialog : IPluginConfigurationDialog
{
/// <inheritdoc />
public abstract Type Type { get; }
}
}

View File

@ -0,0 +1,32 @@
using System.Reactive;
using Artemis.Core;
using ReactiveUI;
namespace Artemis.UI.Avalonia.Shared
{
/// <summary>
/// Represents a view model for a plugin configuration window
/// </summary>
public abstract class PluginConfigurationViewModel : ViewModelBase, IPluginConfigurationViewModel
{
/// <summary>
/// Creates a new instance of the <see cref="PluginConfigurationViewModel" /> class
/// </summary>
/// <param name="plugin"></param>
protected PluginConfigurationViewModel(Plugin plugin)
{
Plugin = plugin;
Close = ReactiveCommand.Create(() => { });
}
/// <summary>
/// Gets the plugin this configuration view model is associated with
/// </summary>
public Plugin Plugin { get; }
/// <summary>
/// A command that closes the window
/// </summary>
public ReactiveCommand<Unit, Unit> Close { get; }
}
}

View File

@ -1,6 +1,5 @@
using System;
using System.Threading.Tasks;
using Artemis.UI.Avalonia.Shared.Utilities;
using Avalonia.Controls;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;

View File

@ -43,6 +43,16 @@ namespace Artemis.UI.Avalonia.Shared.Services.Interfaces
/// <returns>A task containing the return value of type <typeparamref name="TResult" /></returns>
Task<TResult> ShowDialogAsync<TViewModel, TResult>(params (string name, object value)[] parameters) where TViewModel : DialogViewModelBase<TResult>;
/// <summary>
/// Shows a content dialog asking the user to confirm an action
/// </summary>
/// <param name="title">The title of the dialog</param>
/// <param name="message">The message of the dialog</param>
/// <param name="confirm">The text of the confirm button</param>
/// <param name="cancel">The text of the cancel button, if <see langword="null"/> the cancel button will not be shown</param>
/// <returns>A task containing the result of the dialog, <see langword="true"/> if confirmed; otherwise <see langword="false"/></returns>
Task<bool> ShowConfirmContentDialog(string title, string message, string confirm = "Confirm", string? cancel = "Cancel");
/// <summary>
/// Creates an open file dialog, use the fluent API to configure it
/// </summary>
@ -57,8 +67,6 @@ namespace Artemis.UI.Avalonia.Shared.Services.Interfaces
ContentDialogBuilder CreateContentDialog();
ConfirmDialogBuilder CreateConfirmDialog();
Window GetCurrentWindow();
}
}

View File

@ -1,4 +1,6 @@
using Avalonia;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Avalonia.Shared.Services
{

View File

@ -7,6 +7,7 @@ using Artemis.UI.Avalonia.Shared.Services.Interfaces;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using FluentAvalonia.UI.Controls;
using Ninject;
using Ninject.Parameters;
@ -57,6 +58,18 @@ namespace Artemis.UI.Avalonia.Shared.Services
return await ShowDialogAsync(viewModel);
}
public async Task<bool> ShowConfirmContentDialog(string title, string message, string confirm = "Confirm", string? cancel = "Cancel")
{
ContentDialogResult contentDialogResult = await CreateContentDialog()
.WithTitle(title)
.WithContent(message)
.HavingPrimaryButton(b => b.WithText(confirm))
.WithCloseButtonText(cancel)
.ShowAsync();
return contentDialogResult == ContentDialogResult.Primary;
}
public async Task<TResult> ShowDialogAsync<TResult>(DialogViewModelBase<TResult> viewModel)
{
if (Application.Current.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime classic)

View File

@ -22,6 +22,13 @@ namespace Artemis.UI.Avalonia.Ninject.Factories
InputMappingsTabViewModel InputMappingsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds);
}
public interface ISettingsVmFactory : IVmFactory
{
PluginSettingsViewModel CreatePluginSettingsViewModel(Plugin plugin);
PluginFeatureViewModel CreatePluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo, bool showShield);
// DeviceSettingsViewModel CreateDeviceSettingsViewModel(ArtemisDevice device);
}
public interface ISidebarVmFactory : IVmFactory
{
SidebarViewModel SidebarViewModel(IScreen hostScreen);

View File

@ -2,6 +2,7 @@
using Artemis.UI.Avalonia.Ninject.Factories;
using Artemis.UI.Avalonia.Screens;
using Artemis.UI.Avalonia.Services.Interfaces;
using Artemis.UI.Avalonia.Shared;
using Ninject.Extensions.Conventions;
using Ninject.Modules;
using Ninject.Planning.Bindings.Resolvers;

View File

@ -5,6 +5,7 @@ using Artemis.UI.Avalonia.Screens.Debugger.Tabs.Logs;
using Artemis.UI.Avalonia.Screens.Debugger.Tabs.Performance;
using Artemis.UI.Avalonia.Screens.Debugger.Tabs.Render;
using Artemis.UI.Avalonia.Services.Interfaces;
using Artemis.UI.Avalonia.Shared;
using FluentAvalonia.UI.Controls;
using Ninject;
using Ninject.Parameters;

View File

@ -1,4 +1,5 @@
using ReactiveUI;
using Artemis.UI.Avalonia.Shared;
using ReactiveUI;
namespace Artemis.UI.Avalonia.Screens.Debugger.Tabs.DataModel
{

View File

@ -1,4 +1,5 @@
using ReactiveUI;
using Artemis.UI.Avalonia.Shared;
using ReactiveUI;
namespace Artemis.UI.Avalonia.Screens.Debugger.Tabs.Logs
{

View File

@ -1,4 +1,5 @@
using ReactiveUI;
using Artemis.UI.Avalonia.Shared;
using ReactiveUI;
namespace Artemis.UI.Avalonia.Screens.Debugger.Tabs.Performance
{

View File

@ -3,6 +3,7 @@ using System.Reactive.Disposables;
using System.Timers;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Avalonia.Shared;
using ReactiveUI;
using SkiaSharp;

View File

@ -1,12 +1,13 @@
using System.Collections.ObjectModel;
using Artemis.Core;
using Artemis.UI.Avalonia.Ninject.Factories;
using Artemis.UI.Avalonia.Shared;
using RGB.NET.Core;
using ArtemisLed = Artemis.Core.ArtemisLed;
namespace Artemis.UI.Avalonia.Screens.Device
{
public class DevicePropertiesViewModel : ActivatableViewModelBase
public class DevicePropertiesViewModel : DialogViewModelBase<object>
{
public DevicePropertiesViewModel(ArtemisDevice device, IDeviceVmFactory deviceVmFactory)
{

View File

@ -1,5 +1,6 @@
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.UI.Avalonia.Shared;
using Avalonia;
using RGB.NET.Core;

View File

@ -4,6 +4,7 @@ using System.Collections.Specialized;
using System.Linq;
using System.Reactive.Disposables;
using Artemis.Core;
using Artemis.UI.Avalonia.Shared;
using DynamicData.Binding;
using ReactiveUI;

View File

@ -4,6 +4,7 @@ using System.ComponentModel;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Avalonia.Shared;
using Artemis.UI.Avalonia.Shared.Services.Builders;
using Artemis.UI.Avalonia.Shared.Services.Interfaces;
using ReactiveUI;

View File

@ -5,6 +5,7 @@ using System.Linq;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Avalonia.Exceptions;
using Artemis.UI.Avalonia.Shared;
using ReactiveUI;
using RGB.NET.Core;

View File

@ -1,4 +1,5 @@
using ReactiveUI;
using Artemis.UI.Avalonia.Shared;
using ReactiveUI;
namespace Artemis.UI.Avalonia.Screens
{

View File

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
@ -70,7 +69,7 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
{
try
{
Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", Path.Combine(Constants.DataFolder, "Logs"));
Utilities.OpenFolder(Path.Combine(Constants.DataFolder, "logs"));
}
catch (Exception e)
{
@ -80,21 +79,21 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
public void ViewLoadException()
{
if (LoadException != null)
if (LoadException != null)
_windowService.ShowExceptionDialog("Feature failed to enable", LoadException);
}
public async Task InstallPrerequisites()
{
if (FeatureInfo.Prerequisites.Any())
await PluginPrerequisitesInstallDialogViewModel.Show(_dialogService, new List<IPrerequisitesSubject> {FeatureInfo});
if (FeatureInfo.Prerequisites.Any())
await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, new List<IPrerequisitesSubject> {FeatureInfo});
}
public async Task RemovePrerequisites()
{
if (FeatureInfo.Prerequisites.Any(p => p.UninstallActions.Any()))
{
await PluginPrerequisitesUninstallDialogViewModel.Show(_dialogService, new List<IPrerequisitesSubject> {FeatureInfo});
await PluginPrerequisitesUninstallDialogViewModel.Show(_windowService, new List<IPrerequisitesSubject> {FeatureInfo});
this.RaisePropertyChanged(nameof(IsEnabled));
}
}
@ -142,7 +141,7 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
{
if (FeatureInfo.Plugin.Info.RequiresAdmin && !_coreService.IsElevated)
{
bool confirmed = await _dialogService.ShowConfirmDialog("Enable feature", "The plugin of this feature requires admin rights, are you sure you want to enable it?");
bool confirmed = await _windowService.ShowConfirmContentDialog("Enable feature", "The plugin of this feature requires admin rights, are you sure you want to enable it?");
if (!confirmed)
{
this.RaisePropertyChanged(nameof(IsEnabled));
@ -153,7 +152,7 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
// Check if all prerequisites are met async
if (!FeatureInfo.ArePrerequisitesMet())
{
await PluginPrerequisitesInstallDialogViewModel.Show(_dialogService, new List<IPrerequisitesSubject> {FeatureInfo});
await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, new List<IPrerequisitesSubject> {FeatureInfo});
if (!FeatureInfo.ArePrerequisitesMet())
{
this.RaisePropertyChanged(nameof(IsEnabled));
@ -185,20 +184,14 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
private void OnFeatureEnabling(object? sender, PluginFeatureEventArgs e)
{
if (e.PluginFeature != FeatureInfo.Instance)
{
return;
}
if (e.PluginFeature != FeatureInfo.Instance) return;
Enabling = true;
}
private void OnFeatureEnableStopped(object? sender, PluginFeatureEventArgs e)
{
if (e.PluginFeature != FeatureInfo.Instance)
{
return;
}
if (e.PluginFeature != FeatureInfo.Instance) return;
Enabling = false;

View File

@ -5,8 +5,6 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
{
public class PluginPrerequisiteActionViewModel : ViewModelBase
{
public PluginPrerequisiteActionViewModel(PluginPrerequisiteAction action)
{
Action = action;

View File

@ -5,23 +5,22 @@ using System.Threading;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.UI.Avalonia.Shared;
using DynamicData;
using ReactiveUI;
namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
{
public class PluginPrerequisiteViewModel : ActivatableViewModelBase
{
private readonly bool _uninstall;
private readonly ObservableAsPropertyHelper<bool> _busy;
private readonly ObservableAsPropertyHelper<int> _activeStepNumber;
private bool _installing;
private bool _uninstalling;
private bool _isMet;
private readonly ObservableAsPropertyHelper<bool> _busy;
private readonly bool _uninstall;
private PluginPrerequisiteActionViewModel? _activeAction;
private bool _installing;
private bool _isMet;
private bool _uninstalling;
public PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, bool uninstall)
{
_uninstall = uninstall;
@ -45,7 +44,7 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
public PluginPrerequisiteActionViewModel? ActiveAction
{
get => _activeAction;
set => this.RaiseAndSetIfChanged(ref _activeAction , value);
set => this.RaiseAndSetIfChanged(ref _activeAction, value);
}
public PluginPrerequisite PluginPrerequisite { get; }
@ -106,6 +105,14 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
}
}
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
if (disposing) PluginPrerequisite.PropertyChanged -= PluginPrerequisiteOnPropertyChanged;
base.Dispose(disposing);
}
private void PluginPrerequisiteOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(PluginPrerequisite.CurrentAction))
@ -120,21 +127,5 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
ActiveAction = activeAction;
}
#region IDisposable
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
if (disposing)
{
PluginPrerequisite.PropertyChanged -= PluginPrerequisiteOnPropertyChanged;
}
base.Dispose(disposing);
}
#endregion
}
}

View File

@ -14,7 +14,7 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
{
public class PluginPrerequisitesInstallDialogViewModel : DialogViewModelBase<bool>
{
private PluginPrerequisiteViewModel _activePrerequisite;
private PluginPrerequisiteViewModel? _activePrerequisite;
private bool _canInstall;
private bool _showFailed;
private bool _showInstall = true;
@ -34,7 +34,7 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
public ObservableCollection<PluginPrerequisiteViewModel> Prerequisites { get; }
public PluginPrerequisiteViewModel ActivePrerequisite
public PluginPrerequisiteViewModel? ActivePrerequisite
{
get => _activePrerequisite;
set => this.RaiseAndSetIfChanged(ref _activePrerequisite, value);

View File

@ -9,7 +9,6 @@ using Artemis.Core.Services;
using Artemis.UI.Avalonia.Ninject.Factories;
using Artemis.UI.Avalonia.Shared;
using Artemis.UI.Avalonia.Shared.Services.Interfaces;
using DynamicData;
using ReactiveUI;
namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
@ -19,9 +18,9 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
private readonly IPluginManagementService _pluginManagementService;
private readonly List<IPrerequisitesSubject> _subjects;
private readonly IWindowService _windowService;
private PluginPrerequisiteViewModel? _activePrerequisite;
private bool _canUninstall;
private bool _isFinished;
private PluginPrerequisiteViewModel? _activePrerequisite;
private CancellationTokenSource? _tokenSource;
public PluginPrerequisitesUninstallDialogViewModel(List<IPrerequisitesSubject> subjects, string cancelLabel, IPrerequisitesVmFactory prerequisitesVmFactory, IWindowService windowService,
@ -61,18 +60,6 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
set => this.RaiseAndSetIfChanged(ref _isFinished, value);
}
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
if (disposing)
{
_tokenSource?.Cancel();
_tokenSource?.Dispose();
}
base.Dispose(disposing);
}
public async Task Uninstall()
{
CanUninstall = false;
@ -80,29 +67,18 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
// Disable all subjects that are plugins, this will disable their features too
foreach (IPrerequisitesSubject prerequisitesSubject in _subjects)
{
if (prerequisitesSubject is PluginInfo pluginInfo)
{
_pluginManagementService.DisablePlugin(pluginInfo.Plugin, true);
}
if (prerequisitesSubject is PluginInfo pluginInfo) _pluginManagementService.DisablePlugin(pluginInfo.Plugin, true);
}
// Disable all subjects that are features if still required
foreach (IPrerequisitesSubject prerequisitesSubject in _subjects)
{
if (prerequisitesSubject is not PluginFeatureInfo featureInfo)
{
continue;
}
if (prerequisitesSubject is not PluginFeatureInfo featureInfo) continue;
// Disable the parent plugin if the feature is AlwaysEnabled
if (featureInfo.AlwaysEnabled)
{
_pluginManagementService.DisablePlugin(featureInfo.Plugin, true);
}
else if (featureInfo.Instance != null)
{
_pluginManagementService.DisablePluginFeature(featureInfo.Instance, true);
}
else if (featureInfo.Instance != null) _pluginManagementService.DisablePluginFeature(featureInfo.Instance, true);
}
_tokenSource = new CancellationTokenSource();
@ -112,19 +88,13 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
foreach (PluginPrerequisiteViewModel pluginPrerequisiteViewModel in Prerequisites)
{
pluginPrerequisiteViewModel.IsMet = pluginPrerequisiteViewModel.PluginPrerequisite.IsMet();
if (!pluginPrerequisiteViewModel.IsMet)
{
continue;
}
if (!pluginPrerequisiteViewModel.IsMet) continue;
ActivePrerequisite = pluginPrerequisiteViewModel;
await ActivePrerequisite.Uninstall(_tokenSource.Token);
// Wait after the task finished for the user to process what happened
if (pluginPrerequisiteViewModel != Prerequisites.Last())
{
await Task.Delay(1000);
}
if (pluginPrerequisiteViewModel != Prerequisites.Last()) await Task.Delay(1000);
}
if (Prerequisites.All(p => !p.IsMet))
@ -163,5 +133,17 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
{
return await windowService.ShowDialogAsync<PluginPrerequisitesUninstallDialogViewModel, bool>(("subjects", subjects), ("cancelLabel", cancelLabel));
}
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
if (disposing)
{
_tokenSource?.Cancel();
_tokenSource?.Dispose();
}
base.Dispose(disposing);
}
}
}

View File

@ -1,13 +1,15 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Avalonia.Exceptions;
using Artemis.UI.Avalonia.Ninject.Factories;
using Artemis.UI.Avalonia.Shared;
using Artemis.UI.Avalonia.Shared.Services.Interfaces;
using Ninject;
using ReactiveUI;
@ -16,8 +18,10 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
public class PluginSettingsViewModel : ActivatableViewModelBase
{
private readonly ICoreService _coreService;
private readonly INotificationService _notificationService;
private readonly IPluginManagementService _pluginManagementService;
private readonly ISettingsVmFactory _settingsVmFactory;
private readonly IWindowService _windowService;
private bool _canInstallPrerequisites;
private bool _canRemovePrerequisites;
private bool _enabling;
@ -27,13 +31,24 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
public PluginSettingsViewModel(Plugin plugin,
ISettingsVmFactory settingsVmFactory,
ICoreService coreService,
IWindowService windowService,
INotificationService notificationService,
IPluginManagementService pluginManagementService)
{
Plugin = plugin;
_plugin = plugin;
_settingsVmFactory = settingsVmFactory;
_coreService = coreService;
_windowService = windowService;
_notificationService = notificationService;
_pluginManagementService = pluginManagementService;
PluginFeatures = new ObservableCollection<PluginFeatureViewModel>();
foreach (PluginFeatureInfo pluginFeatureInfo in Plugin.Features)
PluginFeatures.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo, false));
_pluginManagementService.PluginDisabled += PluginManagementServiceOnPluginToggled;
_pluginManagementService.PluginEnabled += PluginManagementServiceOnPluginToggled;
}
public ObservableCollection<PluginFeatureViewModel> PluginFeatures { get; }
@ -49,7 +64,7 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
get => _enabling;
set => this.RaiseAndSetIfChanged(ref _enabling, value);
}
public string Type => Plugin.GetType().BaseType?.Name ?? Plugin.GetType().Name;
public bool CanOpenSettings => IsEnabled && Plugin.ConfigurationDialog != null;
@ -83,18 +98,20 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
public void OpenSettings()
{
PluginConfigurationDialog configurationViewModel = (PluginConfigurationDialog) Plugin.ConfigurationDialog;
if (configurationViewModel == null)
if (Plugin.ConfigurationDialog == null)
return;
try
{
PluginConfigurationViewModel viewModel = (PluginConfigurationViewModel) Plugin.Kernel.Get(configurationViewModel.Type);
_windowManager.ShowWindow(new PluginSettingsWindowViewModel(viewModel));
PluginConfigurationViewModel? viewModel = Plugin.Kernel!.Get(Plugin.ConfigurationDialog.Type) as PluginConfigurationViewModel;
if (viewModel == null)
throw new ArtemisUIException($"The type of a plugin configuration dialog must inherit {nameof(PluginConfigurationViewModel)}");
_windowService.ShowWindow(new PluginSettingsWindowViewModel(viewModel));
}
catch (Exception e)
{
_dialogService.ShowExceptionDialog("An exception occured while trying to show the plugin's settings window", e);
_windowService.ShowExceptionDialog("An exception occured while trying to show the plugin's settings window", e);
throw;
}
}
@ -103,11 +120,11 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
{
try
{
Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", Plugin.Directory.FullName);
Utilities.OpenFolder(Plugin.Directory.FullName);
}
catch (Exception e)
{
_dialogService.ShowExceptionDialog("Welp, we couldn't open the device's plugin folder for you", e);
_windowService.ShowExceptionDialog("Welp, we couldn't open the device's plugin folder for you", e);
}
}
@ -116,16 +133,16 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
bool wasEnabled = IsEnabled;
_pluginManagementService.UnloadPlugin(Plugin);
Items.Clear();
PluginFeatures.Clear();
Plugin = _pluginManagementService.LoadPlugin(Plugin.Directory);
foreach (PluginFeatureInfo pluginFeatureInfo in Plugin.Features)
Items.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo, false));
PluginFeatures.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo, false));
if (wasEnabled)
await UpdateEnabled(true);
_messageService.ShowMessage("Reloaded plugin.");
_notificationService.CreateNotification().WithTitle("Reloaded plugin.").Show();
}
public async Task InstallPrerequisites()
@ -134,7 +151,7 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
subjects.AddRange(Plugin.Features.Where(f => f.AlwaysEnabled));
if (subjects.Any(s => s.Prerequisites.Any()))
await PluginPrerequisitesInstallDialogViewModel.Show(_dialogService, subjects);
await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, subjects);
}
public async Task RemovePrerequisites(bool forPluginRemoval = false)
@ -144,15 +161,15 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
if (subjects.Any(s => s.Prerequisites.Any(p => p.UninstallActions.Any())))
{
await PluginPrerequisitesUninstallDialogViewModel.Show(_dialogService, subjects, forPluginRemoval ? "SKIP, REMOVE PLUGIN" : "CANCEL");
NotifyOfPropertyChange(nameof(IsEnabled));
NotifyOfPropertyChange(nameof(CanOpenSettings));
await PluginPrerequisitesUninstallDialogViewModel.Show(_windowService, subjects, forPluginRemoval ? "Skip, remove plugin" : "Cancel");
this.RaisePropertyChanged(nameof(IsEnabled));
this.RaisePropertyChanged(nameof(CanOpenSettings));
}
}
public async Task RemoveSettings()
{
bool confirmed = await _dialogService.ShowConfirmDialog("Clear plugin settings", "Are you sure you want to clear the settings of this plugin?");
bool confirmed = await _windowService.ShowConfirmContentDialog("Clear plugin settings", "Are you sure you want to clear the settings of this plugin?");
if (!confirmed)
return;
@ -166,12 +183,12 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
if (wasEnabled)
await UpdateEnabled(true);
_messageService.ShowMessage("Cleared plugin settings.");
_notificationService.CreateNotification().WithTitle("Cleared plugin settings.").Show();
}
public async Task Remove()
{
bool confirmed = await _dialogService.ShowConfirmDialog("Remove plugin", "Are you sure you want to remove this plugin?");
bool confirmed = await _windowService.ShowConfirmContentDialog("Remove plugin", "Are you sure you want to remove this plugin?");
if (!confirmed)
return;
@ -184,45 +201,55 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
try
{
_pluginManagementService.RemovePlugin(Plugin, false);
((PluginSettingsTabViewModel) Parent).GetPluginInstances();
}
catch (Exception e)
{
_dialogService.ShowExceptionDialog("Failed to remove plugin", e);
_windowService.ShowExceptionDialog("Failed to remove plugin", e);
throw;
}
_messageService.ShowMessage("Removed plugin.");
_notificationService.CreateNotification().WithTitle("Removed plugin.").Show();
}
public void ShowLogsFolder()
{
try
{
Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", Path.Combine(Constants.DataFolder, "Logs"));
Utilities.OpenFolder(Path.Combine(Constants.DataFolder, "logs"));
}
catch (Exception e)
{
_dialogService.ShowExceptionDialog("Welp, we couldn\'t open the logs folder for you", e);
_windowService.ShowExceptionDialog("Welp, we couldn\'t open the logs folder for you", e);
}
}
public void OpenUri(Uri uri)
{
Core.Utilities.OpenUrl(uri.ToString());
Utilities.OpenUrl(uri.ToString());
}
private void PluginManagementServiceOnPluginToggled(object sender, PluginEventArgs e)
protected override void Dispose(bool disposing)
{
NotifyOfPropertyChange(nameof(IsEnabled));
NotifyOfPropertyChange(nameof(CanOpenSettings));
if (disposing)
{
_pluginManagementService.PluginDisabled -= PluginManagementServiceOnPluginToggled;
_pluginManagementService.PluginEnabled -= PluginManagementServiceOnPluginToggled;
}
base.Dispose(disposing);
}
private void PluginManagementServiceOnPluginToggled(object? sender, PluginEventArgs e)
{
this.RaisePropertyChanged(nameof(IsEnabled));
this.RaisePropertyChanged(nameof(CanOpenSettings));
}
private async Task UpdateEnabled(bool enable)
{
if (IsEnabled == enable)
{
NotifyOfPropertyChange(nameof(IsEnabled));
this.RaisePropertyChanged(nameof(IsEnabled));
return;
}
@ -232,7 +259,7 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
if (Plugin.Info.RequiresAdmin && !_coreService.IsElevated)
{
bool confirmed = await _dialogService.ShowConfirmDialog("Enable plugin", "This plugin requires admin rights, are you sure you want to enable it?");
bool confirmed = await _windowService.ShowConfirmContentDialog("Enable plugin", "This plugin requires admin rights, are you sure you want to enable it?");
if (!confirmed)
{
CancelEnable();
@ -246,7 +273,7 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
if (subjects.Any(s => !s.ArePrerequisitesMet()))
{
await PluginPrerequisitesInstallDialogViewModel.Show(_dialogService, subjects);
await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, subjects);
if (!subjects.All(s => s.ArePrerequisitesMet()))
{
CancelEnable();
@ -262,7 +289,10 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
}
catch (Exception e)
{
_messageService.ShowMessage($"Failed to enable plugin {Plugin.Info.Name}\r\n{e.Message}", "VIEW LOGS", ShowLogsFolder);
_notificationService.CreateNotification()
.WithMessage($"Failed to enable plugin {Plugin.Info.Name}\r\n{e.Message}")
.HavingButton(b => b.WithText("View logs").WithAction(ShowLogsFolder))
.Show();
}
finally
{
@ -271,17 +301,19 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
});
}
else
{
_pluginManagementService.DisablePlugin(Plugin, true);
}
NotifyOfPropertyChange(nameof(IsEnabled));
NotifyOfPropertyChange(nameof(CanOpenSettings));
this.RaisePropertyChanged(nameof(IsEnabled));
this.RaisePropertyChanged(nameof(CanOpenSettings));
}
private void CancelEnable()
{
Enabling = false;
NotifyOfPropertyChange(nameof(IsEnabled));
NotifyOfPropertyChange(nameof(CanOpenSettings));
this.RaisePropertyChanged(nameof(IsEnabled));
this.RaisePropertyChanged(nameof(CanOpenSettings));
}
private void CheckPrerequisites()
@ -291,26 +323,5 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
CanRemovePrerequisites = Plugin.Info.Prerequisites.Any(p => p.UninstallActions.Any()) ||
Plugin.Features.Where(f => f.AlwaysEnabled).Any(f => f.Prerequisites.Any(p => p.UninstallActions.Any()));
}
#region Overrides of Screen
protected override void OnInitialActivate()
{
foreach (PluginFeatureInfo pluginFeatureInfo in Plugin.Features)
Items.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo, false));
_pluginManagementService.PluginDisabled += PluginManagementServiceOnPluginToggled;
_pluginManagementService.PluginEnabled += PluginManagementServiceOnPluginToggled;
base.OnInitialActivate();
}
protected override void OnClose()
{
_pluginManagementService.PluginDisabled -= PluginManagementServiceOnPluginToggled;
_pluginManagementService.PluginEnabled -= PluginManagementServiceOnPluginToggled;
base.OnClose();
}
#endregion
}
}

View File

@ -1,32 +1,20 @@
using System;
using Artemis.Core;
using Artemis.UI.Avalonia.Shared;
namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
{
public class PluginSettingsWindowViewModel : Conductor<PluginConfigurationViewModel>
public class PluginSettingsWindowViewModel : ActivatableViewModelBase
{
private readonly PluginConfigurationViewModel _configurationViewModel;
public PluginSettingsWindowViewModel(PluginConfigurationViewModel configurationViewModel)
{
_configurationViewModel = configurationViewModel ?? throw new ArgumentNullException(nameof(configurationViewModel));
ConfigurationViewModel = configurationViewModel ?? throw new ArgumentNullException(nameof(configurationViewModel));
Plugin = configurationViewModel.Plugin;
DisplayName = $"{Plugin.Info.Name} | Settings";
}
public PluginConfigurationViewModel ConfigurationViewModel { get; }
public Plugin Plugin { get; }
protected override void OnInitialActivate()
{
ActiveItem = _configurationViewModel;
ActiveItem.Closed += ActiveItemOnClosed;
base.OnInitialActivate();
}
private void ActiveItemOnClosed(object sender, CloseEventArgs e)
{
ActiveItem.Closed -= ActiveItemOnClosed;
RequestClose();
}
}
}

View File

@ -4,6 +4,6 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Avalonia.Screens.Plugins.Views.PluginSettingsWindowView"
Title="PluginSettingsWindowView">
Welcome to Avalonia!
Title="{Binding DisplayName}">
<ContentControl Content="{Binding ConfigurationViewModel}"></ContentControl>
</Window>

View File

@ -1,10 +1,14 @@
using System;
using Artemis.UI.Avalonia.Screens.Plugins.ViewModels;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Mixins;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using ReactiveUI;
namespace Artemis.UI.Avalonia.Screens.Plugins.Views
{
public partial class PluginSettingsWindowView : Window
public class PluginSettingsWindowView : ReactiveWindow<PluginSettingsWindowViewModel>
{
public PluginSettingsWindowView()
{
@ -12,6 +16,8 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.Views
#if DEBUG
this.AttachDevTools();
#endif
this.WhenActivated(disposables => { ViewModel!.ConfigurationViewModel.Close.Subscribe(_ => Close()).DisposeWith(disposables); });
}
private void InitializeComponent()
@ -19,4 +25,4 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.Views
AvaloniaXamlLoader.Load(this);
}
}
}
}

View File

@ -1,4 +1,6 @@
namespace Artemis.UI.Avalonia.Screens.ProfileEditor.ViewModels
using Artemis.UI.Avalonia.Shared;
namespace Artemis.UI.Avalonia.Screens.ProfileEditor.ViewModels
{
public class ProfileEditorViewModel : ActivatableViewModelBase
{

View File

@ -1,5 +1,6 @@
using Artemis.Core.Services;
using Artemis.UI.Avalonia.Ninject.Factories;
using Artemis.UI.Avalonia.Shared;
using ReactiveUI;
namespace Artemis.UI.Avalonia.Screens.Root.ViewModels

View File

@ -3,6 +3,7 @@ using System.Linq;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Avalonia.Ninject.Factories;
using Artemis.UI.Avalonia.Shared;
using ReactiveUI;
namespace Artemis.UI.Avalonia.Screens.Root.ViewModels

View File

@ -1,5 +1,6 @@
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Avalonia.Shared;
namespace Artemis.UI.Avalonia.Screens.Root.ViewModels
{

View File

@ -1,4 +1,5 @@
using System;
using Artemis.UI.Avalonia.Shared;
using Material.Icons;
using Ninject;
using Ninject.Parameters;

View File

@ -9,6 +9,7 @@ using Artemis.UI.Avalonia.Screens.Home.ViewModels;
using Artemis.UI.Avalonia.Screens.Settings;
using Artemis.UI.Avalonia.Screens.SurfaceEditor.ViewModels;
using Artemis.UI.Avalonia.Screens.Workshop.ViewModels;
using Artemis.UI.Avalonia.Shared;
using Material.Icons;
using Ninject;
using ReactiveUI;

View File

@ -1,5 +1,6 @@
using System.Collections.ObjectModel;
using Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels;
using Artemis.UI.Avalonia.Shared;
using ReactiveUI;
namespace Artemis.UI.Avalonia.Screens.Settings

View File

@ -1,7 +1,9 @@
using System;
using System.Reactive.Disposables;
using System.Reflection;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.UI.Avalonia.Shared;
using Avalonia.Media.Imaging;
using Flurl.Http;
using ReactiveUI;
@ -19,7 +21,7 @@ namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels
public AboutTabViewModel()
{
DisplayName = "About";
this.WhenActivated((Action<IDisposable> _) => Task.Run(Activate));
this.WhenActivated((CompositeDisposable _) => Task.Run(Activate));
}
public string? Version

View File

@ -1,4 +1,6 @@
namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels
using Artemis.UI.Avalonia.Shared;
namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels
{
public class DevicesTabViewModel : ActivatableViewModelBase
{

View File

@ -11,6 +11,7 @@ using Artemis.Core;
using Artemis.Core.LayerBrushes;
using Artemis.Core.Services;
using Artemis.UI.Avalonia.Services.Interfaces;
using Artemis.UI.Avalonia.Shared;
using ReactiveUI;
using Serilog.Events;

View File

@ -1,10 +1,116 @@
namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels
using System;
using System.Collections.Generic;
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;
using Artemis.UI.Avalonia.Extensions;
using Artemis.UI.Avalonia.Ninject.Factories;
using Artemis.UI.Avalonia.Screens.Plugins.ViewModels;
using Artemis.UI.Avalonia.Shared;
using Artemis.UI.Avalonia.Shared.Services.Builders;
using Artemis.UI.Avalonia.Shared.Services.Interfaces;
using ReactiveUI;
namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels
{
public class PluginsTabViewModel : ActivatableViewModelBase
{
public PluginsTabViewModel()
private readonly IPluginManagementService _pluginManagementService;
private readonly INotificationService _notificationService;
private readonly IWindowService _windowService;
private readonly ISettingsVmFactory _settingsVmFactory;
private string? _searchPluginInput;
private List<PluginSettingsViewModel>? _instances;
public PluginsTabViewModel(IPluginManagementService pluginManagementService, INotificationService notificationService, IWindowService windowService, ISettingsVmFactory settingsVmFactory)
{
_pluginManagementService = pluginManagementService;
_notificationService = notificationService;
_windowService = windowService;
_settingsVmFactory = settingsVmFactory;
DisplayName = "Plugins";
Plugins = new ObservableCollection<PluginSettingsViewModel>();
this.WhenAnyValue(x => x.SearchPluginInput).Throttle(TimeSpan.FromMilliseconds(300)).Subscribe(SearchPlugins);
this.WhenActivated((CompositeDisposable _) => GetPluginInstances());
}
public ObservableCollection<PluginSettingsViewModel> Plugins { get; }
public string? SearchPluginInput
{
get => _searchPluginInput;
set => this.RaiseAndSetIfChanged(ref _searchPluginInput, value);
}
public void OpenUrl(string url) => Utilities.OpenUrl(url);
public async Task ImportPlugin()
{
string[]? files = await _windowService.CreateOpenFileDialog().WithTitle("Import Artemis plugin").HavingFilter(f => f.WithExtension("zip").WithName("ZIP files")).ShowAsync();
if (files == null)
return;
// Take the actual import off of the UI thread
await Task.Run(() =>
{
Plugin plugin = _pluginManagementService.ImportPlugin(files[0]);
GetPluginInstances();
SearchPluginInput = plugin.Info.Name;
// Enable it via the VM to enable the prerequisite dialog
PluginSettingsViewModel pluginViewModel = Plugins.FirstOrDefault(i => i.Plugin == plugin);
if (pluginViewModel is { IsEnabled: false })
pluginViewModel.IsEnabled = true;
_notificationService.CreateNotification()
.WithTitle("Success")
.WithMessage($"Imported plugin: {plugin.Info.Name}")
.WithSeverity(NotificationSeverity.Success)
.Show();
});
}
public void GetPluginInstances()
{
_instances = _pluginManagementService.GetAllPlugins()
.Select(p => _settingsVmFactory.CreatePluginSettingsViewModel(p))
.OrderBy(i => i.Plugin.Info.Name)
.ToList();
SearchPlugins(SearchPluginInput);
}
private void SearchPlugins(string? searchPluginInput)
{
if (_instances == null)
return;
List<PluginSettingsViewModel> instances = _instances;
string? search = searchPluginInput?.ToLower();
if (!string.IsNullOrWhiteSpace(search))
instances = instances.Where(i => i.Plugin.Info.Name.ToLower().Contains(search) ||
i.Plugin.Info.Description != null && i.Plugin.Info.Description.ToLower().Contains(search)).ToList();
foreach (PluginSettingsViewModel pluginSettingsViewModel in instances)
{
if (!Plugins.Contains(pluginSettingsViewModel))
Plugins.Add(pluginSettingsViewModel);
}
foreach (PluginSettingsViewModel pluginSettingsViewModel in Plugins.ToList())
{
if (!instances.Contains(pluginSettingsViewModel))
Plugins.Remove(pluginSettingsViewModel);
}
Plugins.Sort(i => i.Plugin.Info.Name);
}
}
}
}

View File

@ -4,5 +4,5 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Avalonia.Screens.Settings.Tabs.Views.PluginsTabView">
Welcome to Avalonia!
<ItemsControl Items="{Binding Plugins}"></ItemsControl>
</UserControl>

View File

@ -1,9 +1,11 @@
using Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.Views
{
public partial class PluginsTabView : UserControl
public partial class PluginsTabView : ReactiveUserControl<PluginsTabViewModel>
{
public PluginsTabView()
{

View File

@ -1,4 +1,5 @@
using Artemis.Core;
using Artemis.UI.Avalonia.Shared;
using ReactiveUI;
using SkiaSharp;

View File

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Avalonia.Ninject.Factories;
using Artemis.UI.Avalonia.Shared;
using Artemis.UI.Avalonia.Shared.Services.Interfaces;
using Avalonia.Input;
using ReactiveUI;
@ -105,7 +106,7 @@ namespace Artemis.UI.Avalonia.Screens.SurfaceEditor.ViewModels
private async Task ExecuteViewProperties(ArtemisDevice device)
{
await _windowService.ShowDialogAsync<bool>(_deviceVmFactory.DevicePropertiesViewModel(device));
await _windowService.ShowDialogAsync(_deviceVmFactory.DevicePropertiesViewModel(device));
}
private bool Fits(float x, float y)

View File

@ -15,9 +15,7 @@ namespace Artemis.UI.Shared
/// </summary>
public abstract class PluginConfigurationDialog : IPluginConfigurationDialog
{
/// <summary>
/// The type of view model the tab contains
/// </summary>
/// <inheritdoc />
public abstract Type Type { get; }
}
}