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

UI - Some VM restructure and added plugin VMs (WIP)

This commit is contained in:
Robert Beekman 2021-11-06 23:08:36 +01:00
parent 8ebd098186
commit 93cfc0e001
40 changed files with 1249 additions and 61 deletions

View File

@ -26,4 +26,9 @@
<HintPath>..\..\..\RGB.NET\bin\net5.0\RGB.NET.Core.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Update="Services\WindowService\ExceptionDialogView.axaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
</ItemGroup>
</Project>

View File

@ -0,0 +1,2 @@
<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/=services_005Cwindowservice/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@ -1,8 +1,10 @@
using System;
using System.Threading.Tasks;
using Artemis.UI.Avalonia.Shared.Utilities;
using Avalonia.Controls;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using Button = Avalonia.Controls.Button;
namespace Artemis.UI.Avalonia.Shared.Services.Builders
{
@ -36,6 +38,19 @@ namespace Artemis.UI.Avalonia.Shared.Services.Builders
return this;
}
/// <summary>
/// Add a filter to the dialog
/// </summary>
public NotificationBuilder HavingButton(Action<NotificationButtonBuilder> configure)
{
NotificationButtonBuilder builder = new();
configure(builder);
_infoBar.ActionButton = builder.Build();
return this;
}
public NotificationBuilder WithSeverity(NotificationSeverity severity)
{
_infoBar.Severity = (InfoBarSeverity) severity;
@ -71,6 +86,31 @@ namespace Artemis.UI.Avalonia.Shared.Services.Builders
}
}
public class NotificationButtonBuilder
{
private string _text = "Text";
private Action? _action;
public NotificationButtonBuilder WithText(string text)
{
_text = text;
return this;
}
public NotificationButtonBuilder WithAction(Action action)
{
_action = action;
return this;
}
public IControl Build()
{
return _action != null
? new Button {Content = _text, Command = new DelegateCommand(_ => _action())}
: new Button {Content = _text};
}
}
public enum NotificationSeverity
{
Informational,

View File

@ -1,4 +1,5 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
using Artemis.UI.Avalonia.Shared.Services.Builders;
using Avalonia.Controls;
@ -7,20 +8,27 @@ namespace Artemis.UI.Avalonia.Shared.Services.Interfaces
public interface IWindowService : IArtemisSharedUIService
{
/// <summary>
/// Creates a view model instance of type <typeparamref name="T" /> and shows its corresponding View as a window
/// Creates a view model instance of type <typeparamref name="T" /> and shows its corresponding View as a window
/// </summary>
/// <typeparam name="T">The type of view model to create</typeparam>
/// <returns>The created view model</returns>
T ShowWindow<T>();
/// <summary>
/// Given a ViewModel, show its corresponding View as a window
/// Given a ViewModel, show its corresponding View as a window
/// </summary>
/// <param name="viewModel">ViewModel to show the View for</param>
void ShowWindow(object viewModel);
/// <summary>
/// Given a ViewModel, show its corresponding View as a Dialog
/// 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>
void ShowExceptionDialog(string title, Exception exception);
/// <summary>
/// Given a ViewModel, show its corresponding View as a Dialog
/// </summary>
/// <typeparam name="T">The return type</typeparam>
/// <param name="viewModel">ViewModel to show the View for</param>
@ -28,13 +36,13 @@ namespace Artemis.UI.Avalonia.Shared.Services.Interfaces
Task<T> ShowDialogAsync<T>(object viewModel);
/// <summary>
/// Creates an open file dialog, use the fluent API to configure it
/// 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>
OpenFileDialogBuilder CreateOpenFileDialog();
/// <summary>
/// Creates a save file dialog, use the fluent API to configure it
/// 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>
SaveFileDialogBuilder CreateSaveFileDialog();

View File

@ -0,0 +1,9 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Avalonia.Shared.Services.ExceptionDialogView"
Title="ExceptionDialogView">
Eh you got an exception but I didn't write the viewer yet :(
</Window>

View File

@ -0,0 +1,20 @@
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Avalonia.Shared.Services
{
public partial class ExceptionDialogView : ReactiveWindow<ExceptionDialogViewModel>
{
public ExceptionDialogView()
{
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -0,0 +1,12 @@
using System;
namespace Artemis.UI.Avalonia.Shared.Services
{
public class ExceptionDialogViewModel : ActivatableViewModelBase
{
public ExceptionDialogViewModel(string title, Exception exception)
{
}
}
}

View File

@ -14,6 +14,7 @@ namespace Artemis.UI.Avalonia.Shared.Services
internal class WindowService : IWindowService
{
private readonly IKernel _kernel;
private bool _exceptionDialogOpen;
public WindowService(IKernel kernel)
{
@ -42,6 +43,23 @@ namespace Artemis.UI.Avalonia.Shared.Services
window.Show();
}
/// <inheritdoc />
public void ShowExceptionDialog(string title, Exception exception)
{
if (_exceptionDialogOpen)
return;
try
{
_exceptionDialogOpen = true;
ShowDialogAsync<object>(new ExceptionDialogViewModel(title, exception)).GetAwaiter().GetResult();
}
finally
{
_exceptionDialogOpen = false;
}
}
public async Task<T> ShowDialogAsync<T>(object viewModel)
{
if (Application.Current.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime classic)

View File

@ -0,0 +1,60 @@
using System;
using System.Windows.Input;
namespace Artemis.UI.Avalonia.Shared.Utilities
{
/// <summary>
/// Provides a command that simply calls a delegate when invoked
/// </summary>
public class DelegateCommand : ICommand
{
private readonly Predicate<object?>? _canExecute;
private readonly Action<object?> _execute;
/// <summary>
/// Creates a new instance of the <see cref="DelegateCommand" /> class
/// </summary>
/// <param name="execute">The delegate to execute</param>
public DelegateCommand(Action<object?> execute) : this(execute, null)
{
}
/// <summary>
/// Creates a new instance of the <see cref="DelegateCommand" /> class with a predicate indicating whether the command
/// can be executed
/// </summary>
/// <param name="execute">The delegate to execute</param>
/// <param name="canExecute">The predicate that determines whether the command can execute</param>
public DelegateCommand(Action<object?> execute, Predicate<object?>? canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
/// <summary>
/// Invokes the <see cref="CanExecuteChanged" /> event
/// </summary>
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
/// <inheritdoc />
public event EventHandler? CanExecuteChanged;
/// <inheritdoc />
public bool CanExecute(object? parameter)
{
if (_canExecute == null)
return true;
return _canExecute(parameter);
}
/// <inheritdoc />
public void Execute(object? parameter)
{
_execute(parameter);
}
}
}

View File

@ -11,6 +11,9 @@
</ItemGroup>
<ItemGroup>
<None Remove="Assets\Images\home-banner.png" />
<None Remove="Screens\Settings\Tabs\Plugins\Views\PluginFeatureView.xaml" />
<None Remove="Screens\Settings\Tabs\Plugins\Views\PluginSettingsView.xaml" />
<None Remove="Screens\Settings\Tabs\Plugins\Views\PluginSettingsWindowView.xaml" />
<None Remove="Screens\SurfaceEditor\Views\ListDeviceView.xaml" />
<None Remove="Screens\SurfaceEditor\Views\SurfaceDeviceView.xaml" />
</ItemGroup>
@ -59,6 +62,9 @@
<Compile Update="Screens\Root\Views\SidebarView.axaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
<Compile Update="Screens\Settings\Tabs\Plugins\Views\PluginsTabView.axaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
<Compile Update="Screens\Sidebar\Views\SidebarView.axaml.cs">
<DependentUpon>SidebarView.axaml</DependentUpon>
<SubType>Code</SubType>
@ -77,6 +83,18 @@
<Content Include="Assets\Images\Logo\bow.ico" />
</ItemGroup>
<ItemGroup>
<Page Include="Screens\Settings\Tabs\Plugins\Views\PluginFeatureView.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Screens\Settings\Tabs\Plugins\Views\PluginSettingsView.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Screens\Settings\Tabs\Plugins\Views\PluginSettingsWindowView.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Screens\SurfaceEditor\Views\ListDeviceView.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<Generator>MSBuild:Compile</Generator>

View File

@ -6,7 +6,7 @@ using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Avalonia.Ninject.Factories;
using Artemis.UI.Avalonia.Screens.Home.ViewModels;
using Artemis.UI.Avalonia.Screens.Settings.ViewModels;
using Artemis.UI.Avalonia.Screens.Settings;
using Artemis.UI.Avalonia.Screens.SurfaceEditor.ViewModels;
using Artemis.UI.Avalonia.Screens.Workshop.ViewModels;
using Material.Icons;

View File

@ -3,7 +3,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
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.Views.SettingsView">
x:Class="Artemis.UI.Avalonia.Screens.Settings.SettingsView">
<TabControl Margin="12" Items="{Binding SettingTabs}">
<TabControl.ItemTemplate>
<DataTemplate>

View File

@ -1,8 +1,7 @@
using Artemis.UI.Avalonia.Screens.Settings.ViewModels;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Avalonia.Screens.Settings.Views
namespace Artemis.UI.Avalonia.Screens.Settings
{
public class SettingsView : ReactiveUserControl<SettingsViewModel>
{

View File

@ -1,7 +1,8 @@
using System.Collections.ObjectModel;
using Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels;
using ReactiveUI;
namespace Artemis.UI.Avalonia.Screens.Settings.ViewModels
namespace Artemis.UI.Avalonia.Screens.Settings
{
public class SettingsViewModel : MainScreenViewModel
{

View File

@ -0,0 +1,213 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Avalonia.Shared.Services.Builders;
using Artemis.UI.Avalonia.Shared.Services.Interfaces;
using ReactiveUI;
namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.Plugins.ViewModels
{
public class PluginFeatureViewModel : ActivatableViewModelBase
{
private readonly ICoreService _coreService;
private readonly INotificationService _notificationService;
private readonly IPluginManagementService _pluginManagementService;
private readonly IWindowService _windowService;
private bool _enabling;
public PluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo,
bool showShield,
ICoreService coreService,
IWindowService windowService,
INotificationService notificationService,
IPluginManagementService pluginManagementService)
{
_coreService = coreService;
_windowService = windowService;
_notificationService = notificationService;
_pluginManagementService = pluginManagementService;
FeatureInfo = pluginFeatureInfo;
ShowShield = FeatureInfo.Plugin.Info.RequiresAdmin && showShield;
_pluginManagementService.PluginFeatureEnabling += OnFeatureEnabling;
_pluginManagementService.PluginFeatureEnabled += OnFeatureEnableStopped;
_pluginManagementService.PluginFeatureEnableFailed += OnFeatureEnableStopped;
FeatureInfo.Plugin.Enabled += PluginOnToggled;
FeatureInfo.Plugin.Disabled += PluginOnToggled;
}
public PluginFeatureInfo FeatureInfo { get; }
public Exception? LoadException => FeatureInfo.LoadException;
public bool ShowShield { get; }
public bool Enabling
{
get => _enabling;
set => this.RaiseAndSetIfChanged(ref _enabling, value);
}
public bool IsEnabled
{
get => FeatureInfo.Instance != null && FeatureInfo.Instance.IsEnabled;
set => Task.Run(() => UpdateEnabled(value));
}
public bool CanToggleEnabled => FeatureInfo.Plugin.IsEnabled && !FeatureInfo.AlwaysEnabled;
public bool CanInstallPrerequisites => FeatureInfo.Prerequisites.Any();
public bool CanRemovePrerequisites => FeatureInfo.Prerequisites.Any(p => p.UninstallActions.Any());
public bool IsPopupEnabled => CanInstallPrerequisites || CanRemovePrerequisites;
public void ShowLogsFolder()
{
try
{
Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", Path.Combine(Constants.DataFolder, "Logs"));
}
catch (Exception e)
{
_windowService.ShowExceptionDialog("Welp, we couldn\'t open the logs folder for you", e);
}
}
public void ViewLoadException()
{
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});
}
public async Task RemovePrerequisites()
{
if (FeatureInfo.Prerequisites.Any(p => p.UninstallActions.Any()))
{
await PluginPrerequisitesUninstallDialogViewModel.Show(_dialogService, new List<IPrerequisitesSubject> {FeatureInfo});
this.RaisePropertyChanged(nameof(IsEnabled));
}
}
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
if (disposing)
{
_pluginManagementService.PluginFeatureEnabling -= OnFeatureEnabling;
_pluginManagementService.PluginFeatureEnabled -= OnFeatureEnableStopped;
_pluginManagementService.PluginFeatureEnableFailed -= OnFeatureEnableStopped;
FeatureInfo.Plugin.Enabled -= PluginOnToggled;
FeatureInfo.Plugin.Disabled -= PluginOnToggled;
}
base.Dispose(disposing);
}
private async Task UpdateEnabled(bool enable)
{
if (IsEnabled == enable)
{
this.RaisePropertyChanged(nameof(IsEnabled));
return;
}
if (FeatureInfo.Instance == null)
{
this.RaisePropertyChanged(nameof(IsEnabled));
_notificationService.CreateNotification()
.WithMessage($"Feature '{FeatureInfo.Name}' is in a broken state and cannot enable.")
.HavingButton(b => b.WithText("View logs").WithAction(ShowLogsFolder))
.WithSeverity(NotificationSeverity.Error)
.Show();
return;
}
if (enable)
{
Enabling = true;
try
{
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?");
if (!confirmed)
{
this.RaisePropertyChanged(nameof(IsEnabled));
return;
}
}
// Check if all prerequisites are met async
if (!FeatureInfo.ArePrerequisitesMet())
{
await PluginPrerequisitesInstallDialogViewModel.Show(_dialogService, new List<IPrerequisitesSubject> {FeatureInfo});
if (!FeatureInfo.ArePrerequisitesMet())
{
this.RaisePropertyChanged(nameof(IsEnabled));
return;
}
}
await Task.Run(() => _pluginManagementService.EnablePluginFeature(FeatureInfo.Instance!, true));
}
catch (Exception e)
{
_notificationService.CreateNotification()
.WithMessage($"Failed to enable '{FeatureInfo.Name}'.\r\n{e.Message}")
.HavingButton(b => b.WithText("View logs").WithAction(ShowLogsFolder))
.WithSeverity(NotificationSeverity.Error)
.Show();
}
finally
{
Enabling = false;
}
}
else
{
_pluginManagementService.DisablePluginFeature(FeatureInfo.Instance, true);
this.RaisePropertyChanged(nameof(IsEnabled));
}
}
private void OnFeatureEnabling(object? sender, PluginFeatureEventArgs e)
{
if (e.PluginFeature != FeatureInfo.Instance)
{
return;
}
Enabling = true;
}
private void OnFeatureEnableStopped(object? sender, PluginFeatureEventArgs e)
{
if (e.PluginFeature != FeatureInfo.Instance)
{
return;
}
Enabling = false;
this.RaisePropertyChanged(nameof(IsEnabled));
this.RaisePropertyChanged(nameof(LoadException));
}
private void PluginOnToggled(object? sender, EventArgs e)
{
this.RaisePropertyChanged(nameof(CanToggleEnabled));
}
}
}

View File

@ -0,0 +1,325 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Avalonia.Screens.Settings.Tabs.Plugins.ViewModels;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.Plugins;
using Artemis.UI.Shared.Services;
using Ninject;
using Stylet;
namespace Artemis.UI.Screens.Settings.Tabs.Plugins
{
public class PluginSettingsViewModel : Conductor<PluginFeatureViewModel>.Collection.AllActive
{
private readonly ICoreService _coreService;
private readonly IDialogService _dialogService;
private readonly IMessageService _messageService;
private readonly IPluginManagementService _pluginManagementService;
private readonly ISettingsVmFactory _settingsVmFactory;
private readonly IWindowManager _windowManager;
private bool _canInstallPrerequisites;
private bool _canRemovePrerequisites;
private bool _enabling;
private bool _isSettingsPopupOpen;
private Plugin _plugin;
public PluginSettingsViewModel(Plugin plugin,
ISettingsVmFactory settingsVmFactory,
ICoreService coreService,
IWindowManager windowManager,
IDialogService dialogService,
IPluginManagementService pluginManagementService,
IMessageService messageService)
{
Plugin = plugin;
_settingsVmFactory = settingsVmFactory;
_coreService = coreService;
_windowManager = windowManager;
_dialogService = dialogService;
_pluginManagementService = pluginManagementService;
_messageService = messageService;
}
public Plugin Plugin
{
get => _plugin;
set => SetAndNotify(ref _plugin, value);
}
public bool Enabling
{
get => _enabling;
set => SetAndNotify(ref _enabling, value);
}
public string Type => Plugin.GetType().BaseType?.Name ?? Plugin.GetType().Name;
public bool CanOpenSettings => IsEnabled && Plugin.ConfigurationDialog != null;
public bool IsEnabled
{
get => Plugin.IsEnabled;
set => Task.Run(() => UpdateEnabled(value));
}
public bool IsSettingsPopupOpen
{
get => _isSettingsPopupOpen;
set
{
if (!SetAndNotify(ref _isSettingsPopupOpen, value)) return;
CheckPrerequisites();
}
}
public bool CanInstallPrerequisites
{
get => _canInstallPrerequisites;
set => SetAndNotify(ref _canInstallPrerequisites, value);
}
public bool CanRemovePrerequisites
{
get => _canRemovePrerequisites;
set => SetAndNotify(ref _canRemovePrerequisites, value);
}
public void OpenSettings()
{
PluginConfigurationDialog configurationViewModel = (PluginConfigurationDialog) Plugin.ConfigurationDialog;
if (configurationViewModel == null)
return;
try
{
PluginConfigurationViewModel viewModel = (PluginConfigurationViewModel) Plugin.Kernel.Get(configurationViewModel.Type);
_windowManager.ShowWindow(new PluginSettingsWindowViewModel(viewModel));
}
catch (Exception e)
{
_dialogService.ShowExceptionDialog("An exception occured while trying to show the plugin's settings window", e);
throw;
}
}
public void OpenPluginDirectory()
{
try
{
Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", Plugin.Directory.FullName);
}
catch (Exception e)
{
_dialogService.ShowExceptionDialog("Welp, we couldn't open the device's plugin folder for you", e);
}
}
public async Task Reload()
{
bool wasEnabled = IsEnabled;
_pluginManagementService.UnloadPlugin(Plugin);
Items.Clear();
Plugin = _pluginManagementService.LoadPlugin(Plugin.Directory);
foreach (PluginFeatureInfo pluginFeatureInfo in Plugin.Features)
Items.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo, false));
if (wasEnabled)
await UpdateEnabled(true);
_messageService.ShowMessage("Reloaded plugin.");
}
public async Task InstallPrerequisites()
{
List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
subjects.AddRange(Plugin.Features.Where(f => f.AlwaysEnabled));
if (subjects.Any(s => s.Prerequisites.Any()))
await PluginPrerequisitesInstallDialogViewModel.Show(_dialogService, subjects);
}
public async Task RemovePrerequisites(bool forPluginRemoval = false)
{
List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
subjects.AddRange(!forPluginRemoval ? Plugin.Features.Where(f => f.AlwaysEnabled) : Plugin.Features);
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));
}
}
public async Task RemoveSettings()
{
bool confirmed = await _dialogService.ShowConfirmDialog("Clear plugin settings", "Are you sure you want to clear the settings of this plugin?");
if (!confirmed)
return;
bool wasEnabled = IsEnabled;
if (IsEnabled)
await UpdateEnabled(false);
_pluginManagementService.RemovePluginSettings(Plugin);
if (wasEnabled)
await UpdateEnabled(true);
_messageService.ShowMessage("Cleared plugin settings.");
}
public async Task Remove()
{
bool confirmed = await _dialogService.ShowConfirmDialog("Remove plugin", "Are you sure you want to remove this plugin?");
if (!confirmed)
return;
// If the plugin or any of its features has uninstall actions, offer to run these
List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
subjects.AddRange(Plugin.Features);
if (subjects.Any(s => s.Prerequisites.Any(p => p.UninstallActions.Any())))
await RemovePrerequisites(true);
try
{
_pluginManagementService.RemovePlugin(Plugin, false);
((PluginSettingsTabViewModel) Parent).GetPluginInstances();
}
catch (Exception e)
{
_dialogService.ShowExceptionDialog("Failed to remove plugin", e);
throw;
}
_messageService.ShowMessage("Removed plugin.");
}
public void ShowLogsFolder()
{
try
{
Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", Path.Combine(Constants.DataFolder, "Logs"));
}
catch (Exception e)
{
_dialogService.ShowExceptionDialog("Welp, we couldn\'t open the logs folder for you", e);
}
}
public void OpenUri(Uri uri)
{
Core.Utilities.OpenUrl(uri.ToString());
}
private void PluginManagementServiceOnPluginToggled(object sender, PluginEventArgs e)
{
NotifyOfPropertyChange(nameof(IsEnabled));
NotifyOfPropertyChange(nameof(CanOpenSettings));
}
private async Task UpdateEnabled(bool enable)
{
if (IsEnabled == enable)
{
NotifyOfPropertyChange(nameof(IsEnabled));
return;
}
if (enable)
{
Enabling = true;
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?");
if (!confirmed)
{
CancelEnable();
return;
}
}
// Check if all prerequisites are met async
List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
subjects.AddRange(Plugin.Features.Where(f => f.AlwaysEnabled || f.EnabledInStorage));
if (subjects.Any(s => !s.ArePrerequisitesMet()))
{
await PluginPrerequisitesInstallDialogViewModel.Show(_dialogService, subjects);
if (!subjects.All(s => s.ArePrerequisitesMet()))
{
CancelEnable();
return;
}
}
await Task.Run(() =>
{
try
{
_pluginManagementService.EnablePlugin(Plugin, true, true);
}
catch (Exception e)
{
_messageService.ShowMessage($"Failed to enable plugin {Plugin.Info.Name}\r\n{e.Message}", "VIEW LOGS", ShowLogsFolder);
}
finally
{
Enabling = false;
}
});
}
else
_pluginManagementService.DisablePlugin(Plugin, true);
NotifyOfPropertyChange(nameof(IsEnabled));
NotifyOfPropertyChange(nameof(CanOpenSettings));
}
private void CancelEnable()
{
Enabling = false;
NotifyOfPropertyChange(nameof(IsEnabled));
NotifyOfPropertyChange(nameof(CanOpenSettings));
}
private void CheckPrerequisites()
{
CanInstallPrerequisites = Plugin.Info.Prerequisites.Any() ||
Plugin.Features.Where(f => f.AlwaysEnabled).Any(f => f.Prerequisites.Any());
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

@ -0,0 +1,33 @@
using System;
using Artemis.Core;
using Stylet;
namespace Artemis.UI.Screens.Settings.Tabs.Plugins
{
public class PluginSettingsWindowViewModel : Conductor<PluginConfigurationViewModel>
{
private readonly PluginConfigurationViewModel _configurationViewModel;
public PluginSettingsWindowViewModel(PluginConfigurationViewModel configurationViewModel)
{
_configurationViewModel = configurationViewModel ?? throw new ArgumentNullException(nameof(configurationViewModel));
Plugin = configurationViewModel.Plugin;
}
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

@ -0,0 +1,10 @@
namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels
{
public class PluginsTabViewModel : ActivatableViewModelBase
{
public PluginsTabViewModel()
{
DisplayName = "Plugins";
}
}
}

View File

@ -0,0 +1,8 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
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.Plugins.Views.PluginFeatureView">
Welcome to Avalonia!
</UserControl>

View File

@ -0,0 +1,19 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.Plugins.Views
{
public partial class PluginFeatureView : UserControl
{
public PluginFeatureView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -0,0 +1,106 @@
<UserControl x:Class="Artemis.UI.Screens.Settings.Tabs.Plugins.PluginFeatureView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.Screens.Settings.Tabs.Plugins"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:viewModels="clr-namespace:Artemis.UI.Avalonia.Screens.Settings.Tabs.Plugins.ViewModels"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance viewModels:PluginFeatureViewModel}">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.PopupBox.xaml" />
</ResourceDictionary.MergedDictionaries>
<shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
</ResourceDictionary>
</UserControl.Resources>
<Grid Margin="-3 -8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- Icon column -->
<shared:ArtemisIcon Grid.Column="0"
Icon="{Binding FeatureInfo.ResolvedIcon}"
Width="20"
Height="20"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Visibility="{Binding LoadException, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted, FallbackValue=Collapsed}" />
<Button Grid.Column="0"
Margin="-8"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Visibility="{Binding LoadException, Converter={StaticResource NullToVisibilityConverter}, FallbackValue=Collapsed}"
Style="{StaticResource MaterialDesignIconButton}"
Foreground="#E74C4C"
ToolTip="An exception occurred while enabling this feature, click to view"
Command="{s:Action ViewLoadException}">
<materialDesign:PackIcon Kind="AlertCircle" />
</Button>
<!-- Display name column -->
<TextBlock Grid.Column="1"
Text="{Binding FeatureInfo.Name}"
Style="{StaticResource MaterialDesignTextBlock}"
VerticalAlignment="Center"
TextWrapping="Wrap"
ToolTip="{Binding FeatureInfo.Description}" />
<!-- Enable toggle column -->
<StackPanel Grid.Column="2"
HorizontalAlignment="Right"
Margin="8"
Visibility="{Binding Enabling, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay, FallbackValue=Collapsed}"
Orientation="Horizontal"
VerticalAlignment="Top"
ToolTip="This feature cannot be disabled without disabling the whole plugin"
ToolTipService.IsEnabled="{Binding FeatureInfo.AlwaysEnabled}">
<materialDesign:PackIcon Kind="ShieldHalfFull"
ToolTip="Plugin requires admin rights"
VerticalAlignment="Center"
Margin="0 0 5 0"
Visibility="{Binding ShowShield, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" />
<CheckBox Style="{StaticResource MaterialDesignCheckBox}"
IsChecked="{Binding IsEnabled}"
IsEnabled="{Binding CanToggleEnabled}">
Feature enabled
</CheckBox>
<materialDesign:PopupBox Style="{StaticResource MaterialDesignToolPopupBox}"
Margin="0 -4 -10 -4"
Foreground="{StaticResource MaterialDesignBody}"
IsEnabled="{Binding IsPopupEnabled}">
<StackPanel>
<Button Command="{s:Action InstallPrerequisites}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="CheckAll" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Install prerequisites</TextBlock>
</StackPanel>
</Button>
<Button Command="{s:Action RemovePrerequisites}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="Delete" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Remove prerequisites</TextBlock>
</StackPanel>
</Button>
</StackPanel>
</materialDesign:PopupBox>
</StackPanel>
<StackPanel Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="7"
Visibility="{Binding Enabling, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay, FallbackValue=Collapsed}">
<ProgressBar Style="{StaticResource MaterialDesignCircularProgressBar}" Value="0" IsIndeterminate="True" />
</StackPanel>
</Grid>
</UserControl>

View File

@ -0,0 +1,8 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
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.Plugins.Views.PluginSettingsView">
Welcome to Avalonia!
</UserControl>

View File

@ -0,0 +1,19 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.Plugins.Views
{
public partial class PluginSettingsView : UserControl
{
public PluginSettingsView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -0,0 +1,209 @@
<UserControl x:Class="Artemis.UI.Screens.Settings.Tabs.Plugins.PluginSettingsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:devices="clr-namespace:Artemis.UI.Screens.Settings.Tabs.Plugins"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:behaviors="clr-namespace:Artemis.UI.Behaviors"
d:DataContext="{d:DesignInstance devices:PluginSettingsViewModel}"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.PopupBox.xaml" />
</ResourceDictionary.MergedDictionaries>
<shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
</ResourceDictionary>
</UserControl.Resources>
<materialDesign:Card Width="900">
<Grid Margin="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4*" />
<ColumnDefinition Width="5*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0" Margin="0 10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<shared:ArtemisIcon Icon="{Binding Plugin.Info.ResolvedIcon}"
Width="48"
Height="48"
Margin="0 5 0 0"
Grid.Row="0"
Grid.RowSpan="3"
HorizontalAlignment="Center"
VerticalAlignment="Top" />
<TextBlock Grid.Column="1" Grid.Row="0" Style="{StaticResource MaterialDesignBody2TextBlock}"
behaviors:HighlightTermBehavior.TermToBeHighlighted="{Binding Parent.SearchPluginInput}"
behaviors:HighlightTermBehavior.Text="{Binding Plugin.Info.Name}"
behaviors:HighlightTermBehavior.HighlightForeground="{StaticResource Primary600Foreground}"
behaviors:HighlightTermBehavior.HighlightBackground="{StaticResource Primary600}" />
<TextBlock Grid.Column="1"
Grid.Row="1"
Style="{StaticResource MaterialDesignBody2TextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
Text="{Binding Plugin.Info.Author}"
Visibility="{Binding Plugin.Info.Author, Converter={StaticResource NullToVisibilityConverter}, Mode=OneWay}" />
<TextBlock Grid.Column="1"
Grid.Row="2"
TextWrapping="Wrap"
behaviors:HighlightTermBehavior.TermToBeHighlighted="{Binding Parent.SearchPluginInput}"
behaviors:HighlightTermBehavior.Text="{Binding Plugin.Info.Description}"
behaviors:HighlightTermBehavior.HighlightForeground="{StaticResource Primary600Foreground}"
behaviors:HighlightTermBehavior.HighlightBackground="{StaticResource Primary600}"
Style="{StaticResource MaterialDesignTextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" />
</Grid>
<Grid Grid.Row="1" Grid.Column="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel VerticalAlignment="Bottom" Orientation="Horizontal">
<Button VerticalAlignment="Bottom"
Style="{StaticResource MaterialDesignRaisedButton}"
ToolTip="Open the plugins settings window"
Margin="4"
Command="{s:Action OpenSettings}">
SETTINGS
</Button>
<materialDesign:PopupBox Style="{StaticResource MaterialDesignToolPopupBox}"
Padding="2 0 2 0"
Foreground="{StaticResource MaterialDesignBody}"
IsPopupOpen="{Binding IsSettingsPopupOpen, Mode=TwoWay}">
<StackPanel>
<Button Command="{s:Action OpenPluginDirectory}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="FolderOpen" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Open plugin directory</TextBlock>
</StackPanel>
</Button>
<Button Command="{s:Action Reload}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="Reload" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Reload plugin</TextBlock>
</StackPanel>
</Button>
<Separator />
<Button Command="{s:Action InstallPrerequisites}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="CheckAll" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Install prerequisites</TextBlock>
</StackPanel>
</Button>
<Button Command="{s:Action RemovePrerequisites}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="Delete" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Remove prerequisites</TextBlock>
</StackPanel>
</Button>
<Separator />
<Button Command="{s:Action RemoveSettings}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="DatabaseRemove" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Clear plugin settings</TextBlock>
</StackPanel>
</Button>
<Button Command="{s:Action Remove}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="DeleteForever" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Remove plugin</TextBlock>
</StackPanel>
</Button>
</StackPanel>
</materialDesign:PopupBox>
<Button Height="40"
Width="40"
Style="{StaticResource MaterialDesignIconForegroundButton}"
ToolTip="{Binding Plugin.Info.Website}"
Visibility="{Binding Plugin.Info.Website, Converter={StaticResource NullToVisibilityConverter}, Mode=OneWay}"
Command="{s:Action OpenUri}"
CommandParameter="{Binding Plugin.Info.Website}">
<materialDesign:PackIcon Kind="Web" Width="20" Height="20" />
</Button>
<Button Height="40"
Width="40"
Style="{StaticResource MaterialDesignIconForegroundButton}"
ToolTip="{Binding Plugin.Info.Repository}"
Visibility="{Binding Plugin.Info.Repository, Converter={StaticResource NullToVisibilityConverter}, Mode=OneWay}"
Command="{s:Action OpenUri}"
CommandParameter="{Binding Plugin.Info.Repository}">
<materialDesign:PackIcon Kind="Git" Width="20" Height="20" />
</Button>
</StackPanel>
<CheckBox Grid.Row="0"
Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Margin="8"
Visibility="{Binding Enabling, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}"
Style="{StaticResource MaterialDesignAccentCheckBox}" IsChecked="{Binding IsEnabled}">
<StackPanel Orientation="Horizontal">
<TextBlock>Plugin enabled</TextBlock>
<materialDesign:PackIcon Kind="ShieldHalfFull"
Margin="5 0 0 0"
ToolTip="Plugin requires admin rights"
Visibility="{Binding Plugin.Info.RequiresAdmin, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" />
</StackPanel>
</CheckBox>
<ProgressBar Grid.Row="0"
Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Margin="8"
Visibility="{Binding Enabling, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}"
Style="{StaticResource MaterialDesignCircularProgressBar}" Value="0"
IsIndeterminate="True" />
</Grid>
<Border Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" BorderBrush="{StaticResource MaterialDesignDivider}" BorderThickness="1 0 0 0" Margin="10 0 0 0" Padding="10 0 0 0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Margin="10 10 0 5" Style="{StaticResource MaterialDesignBody2TextBlock}">Plugin features</TextBlock>
<ListBox Grid.Row="1"
MaxHeight="135"
ItemsSource="{Binding Items}"
HorizontalContentAlignment="Stretch"
VirtualizingPanel.ScrollUnit="Pixel">
<b:Interaction.Behaviors>
<shared:ScrollParentWhenAtMax />
</b:Interaction.Behaviors>
<ListBox.ItemTemplate>
<DataTemplate>
<ContentControl s:View.Model="{Binding IsAsync=True}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsTabStop="False" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Border>
</Grid>
</materialDesign:Card>
</UserControl>

View File

@ -0,0 +1,9 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
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.Plugins.Views.PluginSettingsWindowView"
Title="PluginSettingsWindowView">
Welcome to Avalonia!
</Window>

View File

@ -0,0 +1,22 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.Plugins.Views
{
public partial class PluginSettingsWindowView : Window
{
public PluginSettingsWindowView()
{
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -0,0 +1,43 @@
<controls:MaterialWindow x:Class="Artemis.UI.Screens.Settings.Tabs.Plugins.PluginSettingsWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Artemis.UI.Screens.Settings.Tabs.Plugins"
xmlns:controls="clr-namespace:MaterialDesignExtensions.Controls;assembly=MaterialDesignExtensions"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
mc:Ignorable="d"
Title="Plugin Configuration | Artemis"
Background="{DynamicResource MaterialDesignPaper}"
FontFamily="pack://application:,,,/MaterialDesignThemes.Wpf;component/Resources/Roboto/#Roboto"
UseLayoutRounding="True"
Width="800"
Height="800"
d:DesignHeight="800"
d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:PluginSettingsWindowViewModel}"
Icon="/Resources/Images/Logo/bow.ico">
<materialDesign:DialogHost IsTabStop="False"
Focusable="False"
Identifier="PluginSettingsDialog"
DialogTheme="Inherit">
<DockPanel>
<controls:AppBar Type="Dense" Title="{Binding ActiveItem.Plugin.Info.Name}" DockPanel.Dock="Top" Margin="-18 0 0 0" ShowShadow="False">
<controls:AppBar.AppIcon>
<shared:ArtemisIcon Icon="{Binding Plugin.Info.ResolvedIcon}"
Margin="0 5 0 0"
Width="20"
Height="20"
Grid.Row="0"
Grid.RowSpan="3"
HorizontalAlignment="Center"
VerticalAlignment="Top" />
</controls:AppBar.AppIcon>
</controls:AppBar>
<ContentControl s:View.Model="{Binding ActiveItem}" />
</DockPanel>
</materialDesign:DialogHost>
</controls:MaterialWindow>

View File

@ -3,6 +3,6 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
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.Views.PluginsTabView">
x:Class="Artemis.UI.Avalonia.Screens.Settings.Tabs.Views.PluginsTabView">
Welcome to Avalonia!
</UserControl>

View File

@ -1,8 +1,7 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Avalonia.Screens.Settings.Views
namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.Views
{
public partial class PluginsTabView : UserControl
{

View File

@ -6,7 +6,7 @@ using Avalonia.Media.Imaging;
using Flurl.Http;
using ReactiveUI;
namespace Artemis.UI.Avalonia.Screens.Settings.ViewModels
namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels
{
public class AboutTabViewModel : ActivatableViewModelBase
{

View File

@ -0,0 +1,10 @@
namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels
{
public class DevicesTabViewModel : ActivatableViewModelBase
{
public DevicesTabViewModel()
{
DisplayName = "Devices";
}
}
}

View File

@ -14,7 +14,7 @@ using Artemis.UI.Avalonia.Services.Interfaces;
using ReactiveUI;
using Serilog.Events;
namespace Artemis.UI.Avalonia.Screens.Settings.ViewModels
namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels
{
public class GeneralTabViewModel : ActivatableViewModelBase
{

View File

@ -6,7 +6,7 @@
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="1400"
x:Class="Artemis.UI.Avalonia.Screens.Settings.Views.AboutTabView">
x:Class="Artemis.UI.Avalonia.Screens.Settings.Tabs.Views.AboutTabView">
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<StackPanel Margin="15" MaxWidth="800">
<Grid RowDefinitions="*,*" ColumnDefinitions="Auto,*,Auto">

View File

@ -1,10 +1,8 @@
using Artemis.UI.Avalonia.Screens.Settings.ViewModels;
using Avalonia;
using Avalonia.Controls;
using Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Avalonia.Screens.Settings.Views
namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.Views
{
public partial class AboutTabView : ReactiveUserControl<AboutTabViewModel>
{

View File

@ -3,6 +3,6 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
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.Views.DevicesTabView">
x:Class="Artemis.UI.Avalonia.Screens.Settings.Tabs.Views.DevicesTabView">
Welcome to Avalonia!
</UserControl>

View File

@ -1,8 +1,7 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Avalonia.Screens.Settings.Views
namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.Views
{
public partial class DevicesTabView : UserControl
{

View File

@ -7,7 +7,7 @@
xmlns:layerBrushes="clr-namespace:Artemis.Core.LayerBrushes;assembly=Artemis.Core"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="2400"
x:Class="Artemis.UI.Avalonia.Screens.Settings.Views.GeneralTabView">
x:Class="Artemis.UI.Avalonia.Screens.Settings.Tabs.Views.GeneralTabView">
<StackPanel Margin="15" MaxWidth="1000">
<!-- General settings -->

View File

@ -1,10 +1,8 @@
using Artemis.UI.Avalonia.Screens.Settings.ViewModels;
using Avalonia;
using Avalonia.Controls;
using Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Avalonia.Screens.Settings.Views
namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.Views
{
public partial class GeneralTabView : ReactiveUserControl<GeneralTabViewModel>
{

View File

@ -1,16 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Artemis.UI.Avalonia.Screens.Settings.ViewModels
{
public class DevicesTabViewModel : ActivatableViewModelBase
{
public DevicesTabViewModel()
{
DisplayName = "Devices";
}
}
}

View File

@ -1,16 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Artemis.UI.Avalonia.Screens.Settings.ViewModels
{
public class PluginsTabViewModel : ActivatableViewModelBase
{
public PluginsTabViewModel()
{
DisplayName = "Plugins";
}
}
}