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

Setup wizard - Added and auto-show on home open

UI - Slightly darkened W11 effect
This commit is contained in:
Robert 2022-07-29 22:01:36 +02:00
parent 5c1da6d2c4
commit 1210dc3f63
37 changed files with 1177 additions and 487 deletions

View File

@ -38,7 +38,7 @@ namespace Artemis.Core
/// <summary>
/// The full path to the Artemis data folder
/// </summary>
public static readonly string DataFolder = Path.Combine(BaseFolder, "Artemis");
public static readonly string DataFolder = Path.Combine(BaseFolder, "Artemis.Avalonia");
/// <summary>
/// The full path to the Artemis logs folder

View File

@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
using Artemis.Core.DeviceProviders;
using Artemis.Storage.Entities.Plugins;
using McMaster.NETCore.Plugins;
using Ninject;
@ -16,6 +17,7 @@ namespace Artemis.Core
/// </summary>
public class Plugin : CorePropertyChanged, IDisposable
{
private readonly bool _loadedFromStorage;
private readonly List<PluginFeatureInfo> _features;
private readonly List<Profiler> _profilers;
@ -25,9 +27,10 @@ namespace Artemis.Core
{
Info = info;
Directory = directory;
Entity = pluginEntity ?? new PluginEntity {Id = Guid, IsEnabled = true};
Entity = pluginEntity ?? new PluginEntity {Id = Guid};
Info.Plugin = this;
_loadedFromStorage = pluginEntity != null;
_features = new List<PluginFeatureInfo>();
_profilers = new List<Profiler>();
@ -309,6 +312,27 @@ namespace Artemis.Core
{
return Entity.Features.Any(f => f.IsEnabled) || Features.Any(f => f.AlwaysEnabled);
}
internal void AutoEnableIfNew()
{
if (_loadedFromStorage)
return;
// Enabled is preset to true if the plugin meets the following criteria
// - Requires no admin rights
// - No always-enabled device providers
// - Either has no prerequisites or they are all met
Entity.IsEnabled = !Info.RequiresAdmin &&
Features.All(f => !f.AlwaysEnabled || !f.FeatureType.IsAssignableTo(typeof(DeviceProvider))) &&
Info.ArePrerequisitesMet();
if (!Entity.IsEnabled)
return;
// Also auto-enable any non-device provider feature
foreach (PluginFeatureInfo pluginFeatureInfo in Features)
pluginFeatureInfo.Entity.IsEnabled = !pluginFeatureInfo.FeatureType.IsAssignableTo(typeof(DeviceProvider));
}
/// <inheritdoc />
public void Dispose()
@ -316,5 +340,7 @@ namespace Artemis.Core
Dispose(true);
GC.SuppressFinalize(this);
}
}
}

View File

@ -91,7 +91,7 @@ namespace Artemis.Core
}
/// <summary>
/// The name of the plugin
/// The name of the feature
/// </summary>
[JsonProperty(Required = Required.Always)]
public string Name
@ -101,7 +101,7 @@ namespace Artemis.Core
}
/// <summary>
/// A short description of the plugin
/// A short description of the feature
/// </summary>
[JsonProperty]
public string? Description

View File

@ -362,23 +362,25 @@ namespace Artemis.Core.Services
foreach (Type featureType in featureTypes)
{
// Load the enabled state and if not found, default to true
PluginFeatureEntity featureEntity = plugin.Entity.Features.FirstOrDefault(i => i.Type == featureType.FullName) ??
new PluginFeatureEntity {IsEnabled = true, Type = featureType.FullName!};
PluginFeatureEntity featureEntity = plugin.Entity.Features.FirstOrDefault(i => i.Type == featureType.FullName) ?? new PluginFeatureEntity {Type = featureType.FullName!};
PluginFeatureInfo feature = new(plugin, featureType, featureEntity, (PluginFeatureAttribute?) Attribute.GetCustomAttribute(featureType, typeof(PluginFeatureAttribute)));
// If the plugin only has a single feature, it should always be enabled
if (featureTypes.Count == 1)
feature.AlwaysEnabled = true;
plugin.AddFeature(feature);
}
if (!featureTypes.Any())
_logger.Warning("Plugin {plugin} contains no features", plugin);
// It is appropriate to call this now that we have the features of this plugin
plugin.AutoEnableIfNew();
List<Type> bootstrappers = plugin.Assembly.GetTypes().Where(t => typeof(PluginBootstrapper).IsAssignableFrom(t)).ToList();
if (bootstrappers.Count > 1)
_logger.Warning($"{plugin} has more than one bootstrapper, only initializing {bootstrappers.First().FullName}");
_logger.Warning("{Plugin} has more than one bootstrapper, only initializing {FullName}", plugin, bootstrappers.First().FullName);
if (bootstrappers.Any())
{
plugin.Bootstrapper = (PluginBootstrapper?) Activator.CreateInstance(bootstrappers.First());
@ -398,7 +400,7 @@ namespace Artemis.Core.Services
{
if (!plugin.Info.IsCompatible)
throw new ArtemisPluginException(plugin, $"This plugin only supports the following operating system(s): {plugin.Info.Platforms}");
if (plugin.Assembly == null)
throw new ArtemisPluginException(plugin, "Cannot enable a plugin that hasn't successfully been loaded");

View File

@ -64,7 +64,7 @@ namespace Artemis.UI.Shared
TransparencyLevelHint = WindowTransparencyLevel.Mica;
Color2 color = this.TryFindResource("SolidBackgroundFillColorBase", out object? value) ? (Color) value : new Color2(32, 32, 32);
color = color.LightenPercent(-0.1f);
color = color.LightenPercent(-0.5f);
Background = new ImmutableSolidColorBrush(color, 0.82);
}

View File

@ -4,6 +4,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="800"
x:Class="Artemis.UI.Shared.Services.ExceptionDialogView"
Icon="/Assets/Images/Logo/application.ico"
Title="{Binding Title}"
ExtendClientAreaToDecorationsHint="True"
Width="800"

View File

@ -20,7 +20,7 @@
<PackageReference Include="Avalonia.Controls.PanAndZoom" Version="10.14.0" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.15" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.15"/>
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.15" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.15" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.14" />
<PackageReference Include="DynamicData" Version="7.8.6" />

View File

@ -6,7 +6,8 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.MainWindow"
Icon="/Assets/Images/Logo/application.ico"
Title="Artemis 2.0">
Title="Artemis 2.0"
WindowStartupLocation="CenterScreen">
<Panel Name="RootPanel">
<Border Name="DragHandle" Background="Transparent" Height="40" HorizontalAlignment="Stretch" VerticalAlignment="Top"/>
<DockPanel>

View File

@ -1,4 +1,5 @@
using System.Collections.ObjectModel;
using System.Reactive;
using Artemis.Core;
using Artemis.Core.LayerBrushes;
using Artemis.Core.LayerEffects;
@ -42,10 +43,9 @@ public interface IDeviceVmFactory : IVmFactory
public interface ISettingsVmFactory : IVmFactory
{
PluginSettingsViewModel CreatePluginSettingsViewModel(Plugin plugin);
PluginFeatureViewModel CreatePluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo, bool showShield);
// DeviceSettingsViewModel CreateDeviceSettingsViewModel(ArtemisDevice device);
PluginSettingsViewModel PluginSettingsViewModel(Plugin plugin);
PluginViewModel PluginViewModel(Plugin plugin, ReactiveCommand<Unit, Unit>? reload);
PluginFeatureViewModel PluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo, bool showShield);
}
public interface ISidebarVmFactory : IVmFactory

View File

@ -1,12 +1,19 @@
using ReactiveUI;
using Artemis.Core.Services;
using Artemis.UI.Screens.StartupWizard;
using Artemis.UI.Shared.Services;
using Avalonia.Threading;
using ReactiveUI;
namespace Artemis.UI.Screens.Home
namespace Artemis.UI.Screens.Home;
public class HomeViewModel : MainScreenViewModel
{
public class HomeViewModel : MainScreenViewModel
public HomeViewModel(IScreen hostScreen, ISettingsService settingsService, IWindowService windowService) : base(hostScreen, "home")
{
public HomeViewModel(IScreen hostScreen) : base(hostScreen, "home")
{
DisplayName = "Home";
}
DisplayName = "Home";
// Show the startup wizard if it hasn't been completed
if (!settingsService.GetSetting("UI.SetupWizardCompleted", false).Value)
Dispatcher.UIThread.InvokeAsync(async () => await windowService.ShowDialogAsync<StartupWizardViewModel, bool>());
}
}

View File

@ -2,127 +2,15 @@
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:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:plugins="clr-namespace:Artemis.UI.Screens.Plugins"
mc:Ignorable="d" d:DesignWidth="900" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Plugins.PluginSettingsView"
x:DataType="plugins:PluginSettingsViewModel">
<Border Classes="card" Padding="15" Margin="0 5">
<Grid RowDefinitions="*,Auto" ColumnDefinitions="4*,5*">
<Grid Grid.Row="0" RowDefinitions="Auto,Auto,*" ColumnDefinitions="80,*, Auto">
<shared:ArtemisIcon Icon="{CompiledBinding Plugin.Info.ResolvedIcon}"
Fill="False"
Width="48"
Height="48"
Margin="0 5 0 0"
Grid.Row="0"
Grid.RowSpan="3"
VerticalAlignment="Top" />
<Grid ColumnDefinitions="4*,5*">
<ContentControl Grid.Column="0" Content="{CompiledBinding PluginViewModel}" />
<TextBlock Grid.Column="1" Grid.Row="0" Classes="h5 no-margin" Text="{CompiledBinding Plugin.Info.Name}" />
<ItemsControl Grid.Column="2" Grid.Row="0" IsVisible="{CompiledBinding Platforms.Count}" Items="{CompiledBinding Platforms}" HorizontalAlignment="Right">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Spacing="5" Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<TextBlock Grid.Column="1"
Grid.ColumnSpan="2"
Grid.Row="1"
Classes="subtitle"
Text="{CompiledBinding Plugin.Info.Author}"
IsVisible="{CompiledBinding Plugin.Info.Author, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
<TextBlock Grid.Column="1"
Grid.ColumnSpan="2"
Grid.Row="2"
TextWrapping="Wrap"
Margin="0 5"
Text="{CompiledBinding Plugin.Info.Description}" />
</Grid>
<Grid Grid.Row="1" Grid.Column="0" ColumnDefinitions="*,Auto">
<StackPanel Orientation="Horizontal">
<controls:SplitButton Content="Settings" Command="{CompiledBinding OpenSettings}">
<controls:SplitButton.Flyout>
<MenuFlyout Placement="Bottom" IsOpen="{CompiledBinding IsSettingsPopupOpen, Mode=OneWayToSource}">
<MenuItem Header="Open plugin directory" Command="{CompiledBinding OpenPluginDirectory}">
<MenuItem.Icon>
<avalonia:MaterialIcon Kind="FolderOpen" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Reload plugin" Command="{CompiledBinding Reload}">
<MenuItem.Icon>
<avalonia:MaterialIcon Kind="Reload" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Install prerequisites" Command="{CompiledBinding InstallPrerequisites}">
<MenuItem.Icon>
<avalonia:MaterialIcon Kind="CheckAll" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Remove prerequisites" Command="{CompiledBinding RemovePrerequisites}">
<MenuItem.Icon>
<avalonia:MaterialIcon Kind="Delete" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Clear plugin settings" Command="{CompiledBinding RemoveSettings}">
<MenuItem.Icon>
<avalonia:MaterialIcon Kind="DatabaseRemove" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Remove plugin" Command="{CompiledBinding Remove}">
<MenuItem.Icon>
<avalonia:MaterialIcon Kind="DeleteForever" />
</MenuItem.Icon>
</MenuItem>
</MenuFlyout>
</controls:SplitButton.Flyout>
</controls:SplitButton>
<controls:HyperlinkButton Classes="icon-button icon-button-large"
Margin="5 0"
IsVisible="{CompiledBinding Plugin.Info.Website, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
NavigateUri="{CompiledBinding Plugin.Info.Website}">
<avalonia:MaterialIcon Kind="Web" />
</controls:HyperlinkButton>
<controls:HyperlinkButton Classes="icon-button icon-button-large"
Margin="5 0"
IsVisible="{CompiledBinding Plugin.Info.Repository, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
NavigateUri="{CompiledBinding Plugin.Info.Repository}">
<avalonia:MaterialIcon Kind="Git" />
</controls:HyperlinkButton>
</StackPanel>
<CheckBox Name="EnabledToggle"
Grid.Row="0"
Grid.Column="1"
HorizontalAlignment="Right"
IsVisible="{CompiledBinding !Enabling}"
IsChecked="{CompiledBinding IsEnabled, Mode=OneWay}"
IsEnabled="{CompiledBinding Plugin.Info.IsCompatible}">
<StackPanel x:Name="EnableText" Orientation="Horizontal">
<TextBlock>Enable plugin</TextBlock>
<avalonia:MaterialIcon Kind="ShieldHalfFull"
Margin="5 0 0 0"
ToolTip.Tip="Plugin requires admin rights"
IsVisible="{CompiledBinding Plugin.Info.RequiresAdmin}" />
</StackPanel>
</CheckBox>
<ProgressBar Grid.Row="0"
Grid.Column="1"
HorizontalAlignment="Right"
IsVisible="{CompiledBinding Enabling}"
IsIndeterminate="True" />
</Grid>
<Border Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" BorderBrush="{DynamicResource ButtonBorderBrush}" BorderThickness="1 0 0 0" Margin="10 0 0 0" Padding="10 0 0 0">
<Border Grid.Column="1" BorderBrush="{DynamicResource ButtonBorderBrush}" BorderThickness="1 0 0 0" Margin="10 0 0 0" Padding="10 0 0 0">
<Grid RowDefinitions="Auto,*">
<TextBlock Classes="h5">Plugin features</TextBlock>
<ListBox Grid.Row="1" MaxHeight="135" Items="{CompiledBinding PluginFeatures}" />

View File

@ -1,30 +1,18 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using Avalonia.Threading;
namespace Artemis.UI.Screens.Plugins
{
public partial class PluginSettingsView : ReactiveUserControl<PluginSettingsViewModel>
{
private readonly CheckBox _enabledToggle;
public PluginSettingsView()
{
InitializeComponent();
_enabledToggle = this.Find<CheckBox>("EnabledToggle");
_enabledToggle.Click += EnabledToggleOnClick;
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void EnabledToggleOnClick(object? sender, RoutedEventArgs e)
{
Dispatcher.UIThread.Post(() => ViewModel?.UpdateEnabled(!ViewModel.Plugin.IsEnabled));
}
}
}

View File

@ -1,358 +1,65 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Collections.ObjectModel;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Exceptions;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders;
using Avalonia.Controls;
using Avalonia.Threading;
using Material.Icons;
using Material.Icons.Avalonia;
using Ninject;
using ReactiveUI;
namespace Artemis.UI.Screens.Plugins;
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;
private bool _isSettingsPopupOpen;
private Plugin _plugin;
private Window? _window;
private readonly ISettingsVmFactory _settingsVmFactory;
private readonly IPluginManagementService _pluginManagementService;
private readonly INotificationService _notificationService;
private PluginViewModel _pluginViewModel;
public PluginSettingsViewModel(Plugin plugin,
ISettingsVmFactory settingsVmFactory,
ICoreService coreService,
IWindowService windowService,
INotificationService notificationService,
IPluginManagementService pluginManagementService)
public PluginSettingsViewModel(Plugin plugin, ISettingsVmFactory settingsVmFactory, IPluginManagementService pluginManagementService, INotificationService notificationService)
{
_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));
Platforms = new ObservableCollection<PluginPlatformViewModel>();
if (Plugin.Info.Platforms != null)
{
if (Plugin.Info.Platforms.Value.HasFlag(PluginPlatform.Windows))
Platforms.Add(new PluginPlatformViewModel("Windows", MaterialIconKind.MicrosoftWindows));
if (Plugin.Info.Platforms.Value.HasFlag(PluginPlatform.Linux))
Platforms.Add(new PluginPlatformViewModel("Linux", MaterialIconKind.Linux));
if (Plugin.Info.Platforms.Value.HasFlag(PluginPlatform.OSX))
Platforms.Add(new PluginPlatformViewModel("OSX", MaterialIconKind.Apple));
}
_notificationService = notificationService;
Reload = ReactiveCommand.CreateFromTask(ExecuteReload);
OpenSettings = ReactiveCommand.Create(ExecuteOpenSettings, this.WhenAnyValue(vm => vm.IsEnabled, e => e && Plugin.ConfigurationDialog != null));
RemoveSettings = ReactiveCommand.CreateFromTask(ExecuteRemoveSettings);
Remove = ReactiveCommand.CreateFromTask(ExecuteRemove);
InstallPrerequisites = ReactiveCommand.CreateFromTask(ExecuteInstallPrerequisites, this.WhenAnyValue(x => x.CanInstallPrerequisites));
RemovePrerequisites = ReactiveCommand.CreateFromTask<bool>(ExecuteRemovePrerequisites, this.WhenAnyValue(x => x.CanRemovePrerequisites));
ShowLogsFolder = ReactiveCommand.Create(ExecuteShowLogsFolder);
OpenPluginDirectory = ReactiveCommand.Create(ExecuteOpenPluginDirectory);
this.WhenActivated(d =>
{
Plugin.Enabled += OnPluginToggled;
Plugin.Disabled += OnPluginToggled;
Disposable.Create(() =>
{
Plugin.Enabled -= OnPluginToggled;
Plugin.Disabled -= OnPluginToggled;
_window?.Close();
}).DisposeWith(d);
});
PluginViewModel = settingsVmFactory.PluginViewModel(_plugin, Reload);
PluginFeatures = new ObservableCollection<PluginFeatureViewModel>();
foreach (PluginFeatureInfo pluginFeatureInfo in _plugin.Features)
PluginFeatures.Add(settingsVmFactory.PluginFeatureViewModel(pluginFeatureInfo, false));
}
public ReactiveCommand<Unit, Unit> Reload { get; }
public ReactiveCommand<Unit, Unit> OpenSettings { get; }
public ReactiveCommand<Unit, Unit> RemoveSettings { get; }
public ReactiveCommand<Unit, Unit> Remove { get; }
public ReactiveCommand<Unit, Unit> InstallPrerequisites { get; }
public ReactiveCommand<bool, Unit> RemovePrerequisites { get; }
public ReactiveCommand<Unit, Unit> ShowLogsFolder { get; }
public ReactiveCommand<Unit, Unit> OpenPluginDirectory { get; }
public PluginViewModel PluginViewModel
{
get => _pluginViewModel;
private set => RaiseAndSetIfChanged(ref _pluginViewModel, value);
}
public ObservableCollection<PluginFeatureViewModel> PluginFeatures { get; }
public ObservableCollection<PluginPlatformViewModel> Platforms { get; }
public Plugin Plugin
{
get => _plugin;
set => RaiseAndSetIfChanged(ref _plugin, value);
}
public bool Enabling
{
get => _enabling;
set => RaiseAndSetIfChanged(ref _enabling, value);
}
public string Type => Plugin.GetType().BaseType?.Name ?? Plugin.GetType().Name;
public bool IsEnabled => Plugin.IsEnabled;
public bool IsSettingsPopupOpen
{
get => _isSettingsPopupOpen;
set
{
if (!RaiseAndSetIfChanged(ref _isSettingsPopupOpen, value)) return;
CheckPrerequisites();
}
}
public bool CanInstallPrerequisites
{
get => _canInstallPrerequisites;
set => RaiseAndSetIfChanged(ref _canInstallPrerequisites, value);
}
public bool CanRemovePrerequisites
{
get => _canRemovePrerequisites;
set => RaiseAndSetIfChanged(ref _canRemovePrerequisites, value);
}
private void ExecuteOpenSettings()
{
if (Plugin.ConfigurationDialog == null)
return;
if (_window != null)
{
_window.WindowState = WindowState.Normal;
_window.Activate();
return;
}
try
{
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)}");
_window = _windowService.ShowWindow(new PluginSettingsWindowViewModel(viewModel));
_window.Closed += (_, _) => _window = null;
}
catch (Exception e)
{
_windowService.ShowExceptionDialog("An exception occured while trying to show the plugin's settings window", e);
throw;
}
}
private void ExecuteOpenPluginDirectory()
{
try
{
Utilities.OpenFolder(Plugin.Directory.FullName);
}
catch (Exception e)
{
_windowService.ShowExceptionDialog("Welp, we couldn't open the device's plugin folder for you", e);
}
}
private async Task ExecuteReload()
{
bool wasEnabled = IsEnabled;
await Task.Run(() => _pluginManagementService.UnloadPlugin(Plugin));
bool wasEnabled = _plugin.IsEnabled;
await Task.Run(() => _pluginManagementService.UnloadPlugin(_plugin));
PluginFeatures.Clear();
Plugin = _pluginManagementService.LoadPlugin(Plugin.Directory);
foreach (PluginFeatureInfo pluginFeatureInfo in Plugin.Features)
PluginFeatures.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo, false));
_plugin = _pluginManagementService.LoadPlugin(_plugin.Directory);
PluginViewModel = _settingsVmFactory.PluginViewModel(_plugin, Reload);
foreach (PluginFeatureInfo pluginFeatureInfo in _plugin.Features)
PluginFeatures.Add(_settingsVmFactory.PluginFeatureViewModel(pluginFeatureInfo, false));
if (wasEnabled)
await UpdateEnabled(true);
await PluginViewModel.UpdateEnabled(true);
_notificationService.CreateNotification().WithTitle("Reloaded plugin.").Show();
}
private async Task ExecuteInstallPrerequisites()
{
List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
subjects.AddRange(Plugin.Features.Where(f => f.AlwaysEnabled));
if (subjects.Any(s => s.PlatformPrerequisites.Any()))
await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, subjects);
}
private async Task ExecuteRemovePrerequisites(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.PlatformPrerequisites.Any(p => p.UninstallActions.Any())))
{
await PluginPrerequisitesUninstallDialogViewModel.Show(_windowService, subjects, forPluginRemoval ? "Skip, remove plugin" : "Cancel");
}
}
private async Task ExecuteRemoveSettings()
{
bool confirmed = await _windowService.ShowConfirmContentDialog("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);
_notificationService.CreateNotification().WithTitle("Cleared plugin settings.").Show();
}
private async Task ExecuteRemove()
{
bool confirmed = await _windowService.ShowConfirmContentDialog("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.PlatformPrerequisites.Any(p => p.UninstallActions.Any())))
await ExecuteRemovePrerequisites(true);
try
{
_pluginManagementService.RemovePlugin(Plugin, false);
}
catch (Exception e)
{
_windowService.ShowExceptionDialog("Failed to remove plugin", e);
throw;
}
_notificationService.CreateNotification().WithTitle("Removed plugin.").Show();
}
private void ExecuteShowLogsFolder()
{
try
{
Utilities.OpenFolder(Constants.LogsFolder);
}
catch (Exception e)
{
_windowService.ShowExceptionDialog("Welp, we couldn\'t open the logs folder for you", e);
}
}
public async Task UpdateEnabled(bool enable)
{
if (Enabling)
return;
if (!enable)
{
try
{
await Task.Run(() => _pluginManagementService.DisablePlugin(Plugin, true));
}
catch (Exception e)
{
await Dispatcher.UIThread.InvokeAsync(() => _notificationService.CreateNotification()
.WithSeverity(NotificationSeverity.Error)
.WithMessage($"Failed to disable plugin {Plugin.Info.Name}\r\n{e.Message}")
.HavingButton(b => b.WithText("View logs").WithCommand(ShowLogsFolder))
.Show());
}
finally
{
this.RaisePropertyChanged(nameof(IsEnabled));
}
return;
}
try
{
Enabling = true;
if (Plugin.Info.RequiresAdmin && !_coreService.IsElevated)
{
bool confirmed = await _windowService.ShowConfirmContentDialog("Enable plugin", "This plugin requires admin rights, are you sure you want to enable it?");
if (!confirmed)
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(_windowService, subjects);
if (!subjects.All(s => s.ArePrerequisitesMet()))
return;
}
await Task.Run(() => _pluginManagementService.EnablePlugin(Plugin, true, true));
}
catch (Exception e)
{
await Dispatcher.UIThread.InvokeAsync(() => _notificationService.CreateNotification()
.WithSeverity(NotificationSeverity.Error)
.WithMessage($"Failed to enable plugin {Plugin.Info.Name}\r\n{e.Message}")
.HavingButton(b => b.WithText("View logs").WithCommand(ShowLogsFolder))
.Show());
}
finally
{
Enabling = false;
this.RaisePropertyChanged(nameof(IsEnabled));
}
}
private void CheckPrerequisites()
{
CanInstallPrerequisites = Plugin.Info.PlatformPrerequisites.Any() ||
Plugin.Features.Where(f => f.AlwaysEnabled).Any(f => f.PlatformPrerequisites.Any());
CanRemovePrerequisites = Plugin.Info.PlatformPrerequisites.Any(p => p.UninstallActions.Any()) ||
Plugin.Features.Where(f => f.AlwaysEnabled).Any(f => f.PlatformPrerequisites.Any(p => p.UninstallActions.Any()));
}
private void OnPluginToggled(object? sender, EventArgs e)
{
Dispatcher.UIThread.Post(() =>
{
this.RaisePropertyChanged(nameof(IsEnabled));
if (!IsEnabled)
_window?.Close();
});
}
}

View File

@ -0,0 +1,124 @@
<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"
xmlns:plugins="clr-namespace:Artemis.UI.Screens.Plugins"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
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="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Plugins.PluginView"
x:DataType="plugins:PluginViewModel">
<Grid RowDefinitions="*,Auto">
<Grid Grid.Row="0" RowDefinitions="Auto,Auto,*" ColumnDefinitions="80,*, Auto">
<shared:ArtemisIcon Icon="{CompiledBinding Plugin.Info.ResolvedIcon}"
Fill="False"
Width="48"
Height="48"
Margin="0 5 0 0"
Grid.Row="0"
Grid.RowSpan="3"
VerticalAlignment="Top" />
<TextBlock Grid.Column="1" Grid.Row="0" Classes="h5 no-margin" Text="{CompiledBinding Plugin.Info.Name}" />
<ItemsControl Grid.Column="2" Grid.Row="0" IsVisible="{CompiledBinding Platforms.Count}" Items="{CompiledBinding Platforms}" HorizontalAlignment="Right">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Spacing="5" Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<TextBlock Grid.Column="1"
Grid.ColumnSpan="2"
Grid.Row="1"
Classes="subtitle"
Text="{CompiledBinding Plugin.Info.Author}"
IsVisible="{CompiledBinding Plugin.Info.Author, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
<TextBlock Grid.Column="1"
Grid.ColumnSpan="2"
Grid.Row="2"
TextWrapping="Wrap"
Margin="0 5"
Text="{CompiledBinding Plugin.Info.Description}" />
</Grid>
<Grid Grid.Row="1" ColumnDefinitions="*,Auto">
<StackPanel Orientation="Horizontal">
<controls:SplitButton Content="Settings" Command="{CompiledBinding OpenSettings}">
<controls:SplitButton.Flyout>
<MenuFlyout Placement="Bottom" IsOpen="{CompiledBinding IsSettingsPopupOpen, Mode=OneWayToSource}">
<MenuItem Header="Open plugin directory" Command="{CompiledBinding OpenPluginDirectory}">
<MenuItem.Icon>
<avalonia:MaterialIcon Kind="FolderOpen" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Reload plugin" Command="{CompiledBinding Reload}">
<MenuItem.Icon>
<avalonia:MaterialIcon Kind="Reload" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Install prerequisites" Command="{CompiledBinding InstallPrerequisites}">
<MenuItem.Icon>
<avalonia:MaterialIcon Kind="CheckAll" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Remove prerequisites" Command="{CompiledBinding RemovePrerequisites}">
<MenuItem.Icon>
<avalonia:MaterialIcon Kind="Delete" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Clear plugin settings" Command="{CompiledBinding RemoveSettings}">
<MenuItem.Icon>
<avalonia:MaterialIcon Kind="DatabaseRemove" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Remove plugin" Command="{CompiledBinding Remove}">
<MenuItem.Icon>
<avalonia:MaterialIcon Kind="DeleteForever" />
</MenuItem.Icon>
</MenuItem>
</MenuFlyout>
</controls:SplitButton.Flyout>
</controls:SplitButton>
<controls:HyperlinkButton Classes="icon-button icon-button-large"
Margin="5 0"
IsVisible="{CompiledBinding Plugin.Info.Website, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
NavigateUri="{CompiledBinding Plugin.Info.Website}">
<avalonia:MaterialIcon Kind="Web" />
</controls:HyperlinkButton>
<controls:HyperlinkButton Classes="icon-button icon-button-large"
Margin="5 0"
IsVisible="{CompiledBinding Plugin.Info.Repository, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
NavigateUri="{CompiledBinding Plugin.Info.Repository}">
<avalonia:MaterialIcon Kind="Git" />
</controls:HyperlinkButton>
</StackPanel>
<CheckBox Name="EnabledToggle"
Grid.Row="0"
Grid.Column="1"
HorizontalAlignment="Right"
IsVisible="{CompiledBinding !Enabling}"
IsChecked="{CompiledBinding IsEnabled, Mode=OneWay}"
IsEnabled="{CompiledBinding Plugin.Info.IsCompatible}">
<StackPanel x:Name="EnableText" Orientation="Horizontal">
<TextBlock>Enable plugin</TextBlock>
<avalonia:MaterialIcon Kind="ShieldHalfFull"
Margin="5 0 0 0"
ToolTip.Tip="Plugin requires admin rights"
IsVisible="{CompiledBinding Plugin.Info.RequiresAdmin}" />
</StackPanel>
</CheckBox>
<ProgressBar Grid.Row="0"
Grid.Column="1"
HorizontalAlignment="Right"
IsVisible="{CompiledBinding Enabling}"
IsIndeterminate="True" />
</Grid>
</Grid>
</UserControl>

View File

@ -0,0 +1,30 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using Avalonia.Threading;
namespace Artemis.UI.Screens.Plugins;
public partial class PluginView : ReactiveUserControl<PluginViewModel>
{
private CheckBox _enabledToggle;
public PluginView()
{
InitializeComponent();
_enabledToggle = this.Find<CheckBox>("EnabledToggle");
_enabledToggle.Click += EnabledToggleOnClick;
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void EnabledToggleOnClick(object? sender, RoutedEventArgs e)
{
Dispatcher.UIThread.Post(() => ViewModel?.UpdateEnabled(!ViewModel.Plugin.IsEnabled));
}
}

View File

@ -0,0 +1,329 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Exceptions;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders;
using Avalonia.Controls;
using Avalonia.Threading;
using Material.Icons;
using Ninject;
using ReactiveUI;
namespace Artemis.UI.Screens.Plugins;
public class PluginViewModel : ActivatableViewModelBase
{
private readonly ICoreService _coreService;
private readonly INotificationService _notificationService;
private readonly IPluginManagementService _pluginManagementService;
private readonly IWindowService _windowService;
private bool _canInstallPrerequisites;
private bool _canRemovePrerequisites;
private bool _enabling;
private bool _isSettingsPopupOpen;
private Plugin _plugin;
private Window? _window;
public PluginViewModel(Plugin plugin,
ReactiveCommand<Unit, Unit>? reload,
ICoreService coreService,
IWindowService windowService,
INotificationService notificationService,
IPluginManagementService pluginManagementService)
{
_plugin = plugin;
_coreService = coreService;
_windowService = windowService;
_notificationService = notificationService;
_pluginManagementService = pluginManagementService;
Platforms = new ObservableCollection<PluginPlatformViewModel>();
if (Plugin.Info.Platforms != null)
{
if (Plugin.Info.Platforms.Value.HasFlag(PluginPlatform.Windows))
Platforms.Add(new PluginPlatformViewModel("Windows", MaterialIconKind.MicrosoftWindows));
if (Plugin.Info.Platforms.Value.HasFlag(PluginPlatform.Linux))
Platforms.Add(new PluginPlatformViewModel("Linux", MaterialIconKind.Linux));
if (Plugin.Info.Platforms.Value.HasFlag(PluginPlatform.OSX))
Platforms.Add(new PluginPlatformViewModel("OSX", MaterialIconKind.Apple));
}
Reload = reload;
OpenSettings = ReactiveCommand.Create(ExecuteOpenSettings, this.WhenAnyValue(vm => vm.IsEnabled, e => e && Plugin.ConfigurationDialog != null));
RemoveSettings = ReactiveCommand.CreateFromTask(ExecuteRemoveSettings);
Remove = ReactiveCommand.CreateFromTask(ExecuteRemove);
InstallPrerequisites = ReactiveCommand.CreateFromTask(ExecuteInstallPrerequisites, this.WhenAnyValue(x => x.CanInstallPrerequisites));
RemovePrerequisites = ReactiveCommand.CreateFromTask<bool>(ExecuteRemovePrerequisites, this.WhenAnyValue(x => x.CanRemovePrerequisites));
ShowLogsFolder = ReactiveCommand.Create(ExecuteShowLogsFolder);
OpenPluginDirectory = ReactiveCommand.Create(ExecuteOpenPluginDirectory);
this.WhenActivated(d =>
{
Plugin.Enabled += OnPluginToggled;
Plugin.Disabled += OnPluginToggled;
Disposable.Create(() =>
{
Plugin.Enabled -= OnPluginToggled;
Plugin.Disabled -= OnPluginToggled;
_window?.Close();
}).DisposeWith(d);
});
}
public ReactiveCommand<Unit, Unit>? Reload { get; }
public ReactiveCommand<Unit, Unit> OpenSettings { get; }
public ReactiveCommand<Unit, Unit> RemoveSettings { get; }
public ReactiveCommand<Unit, Unit> Remove { get; }
public ReactiveCommand<Unit, Unit> InstallPrerequisites { get; }
public ReactiveCommand<bool, Unit> RemovePrerequisites { get; }
public ReactiveCommand<Unit, Unit> ShowLogsFolder { get; }
public ReactiveCommand<Unit, Unit> OpenPluginDirectory { get; }
public ObservableCollection<PluginPlatformViewModel> Platforms { get; }
public Plugin Plugin
{
get => _plugin;
set => RaiseAndSetIfChanged(ref _plugin, value);
}
public bool Enabling
{
get => _enabling;
set => RaiseAndSetIfChanged(ref _enabling, value);
}
public string Type => Plugin.GetType().BaseType?.Name ?? Plugin.GetType().Name;
public bool IsEnabled => Plugin.IsEnabled;
public bool IsSettingsPopupOpen
{
get => _isSettingsPopupOpen;
set
{
if (!RaiseAndSetIfChanged(ref _isSettingsPopupOpen, value)) return;
CheckPrerequisites();
}
}
public bool CanInstallPrerequisites
{
get => _canInstallPrerequisites;
set => RaiseAndSetIfChanged(ref _canInstallPrerequisites, value);
}
public bool CanRemovePrerequisites
{
get => _canRemovePrerequisites;
set => RaiseAndSetIfChanged(ref _canRemovePrerequisites, value);
}
private void ExecuteOpenSettings()
{
if (Plugin.ConfigurationDialog == null)
return;
if (_window != null)
{
_window.WindowState = WindowState.Normal;
_window.Activate();
return;
}
try
{
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)}");
_window = _windowService.ShowWindow(new PluginSettingsWindowViewModel(viewModel));
_window.Closed += (_, _) => _window = null;
}
catch (Exception e)
{
_windowService.ShowExceptionDialog("An exception occured while trying to show the plugin's settings window", e);
throw;
}
}
private void ExecuteOpenPluginDirectory()
{
try
{
Utilities.OpenFolder(Plugin.Directory.FullName);
}
catch (Exception e)
{
_windowService.ShowExceptionDialog("Welp, we couldn't open the device's plugin folder for you", e);
}
}
private async Task ExecuteInstallPrerequisites()
{
List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
subjects.AddRange(Plugin.Features.Where(f => f.AlwaysEnabled));
if (subjects.Any(s => s.PlatformPrerequisites.Any()))
await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, subjects);
}
private async Task ExecuteRemovePrerequisites(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.PlatformPrerequisites.Any(p => p.UninstallActions.Any())))
{
await PluginPrerequisitesUninstallDialogViewModel.Show(_windowService, subjects, forPluginRemoval ? "Skip, remove plugin" : "Cancel");
}
}
private async Task ExecuteRemoveSettings()
{
bool confirmed = await _windowService.ShowConfirmContentDialog("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);
_notificationService.CreateNotification().WithTitle("Cleared plugin settings.").Show();
}
private async Task ExecuteRemove()
{
bool confirmed = await _windowService.ShowConfirmContentDialog("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.PlatformPrerequisites.Any(p => p.UninstallActions.Any())))
await ExecuteRemovePrerequisites(true);
try
{
_pluginManagementService.RemovePlugin(Plugin, false);
}
catch (Exception e)
{
_windowService.ShowExceptionDialog("Failed to remove plugin", e);
throw;
}
_notificationService.CreateNotification().WithTitle("Removed plugin.").Show();
}
private void ExecuteShowLogsFolder()
{
try
{
Utilities.OpenFolder(Constants.LogsFolder);
}
catch (Exception e)
{
_windowService.ShowExceptionDialog("Welp, we couldn\'t open the logs folder for you", e);
}
}
public async Task UpdateEnabled(bool enable)
{
if (Enabling)
return;
if (!enable)
{
try
{
await Task.Run(() => _pluginManagementService.DisablePlugin(Plugin, true));
}
catch (Exception e)
{
await Dispatcher.UIThread.InvokeAsync(() => _notificationService.CreateNotification()
.WithSeverity(NotificationSeverity.Error)
.WithMessage($"Failed to disable plugin {Plugin.Info.Name}\r\n{e.Message}")
.HavingButton(b => b.WithText("View logs").WithCommand(ShowLogsFolder))
.Show());
}
finally
{
this.RaisePropertyChanged(nameof(IsEnabled));
}
return;
}
try
{
Enabling = true;
if (Plugin.Info.RequiresAdmin && !_coreService.IsElevated)
{
bool confirmed = await _windowService.ShowConfirmContentDialog("Enable plugin", "This plugin requires admin rights, are you sure you want to enable it?");
if (!confirmed)
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(_windowService, subjects);
if (!subjects.All(s => s.ArePrerequisitesMet()))
return;
}
await Task.Run(() => _pluginManagementService.EnablePlugin(Plugin, true, true));
}
catch (Exception e)
{
await Dispatcher.UIThread.InvokeAsync(() => _notificationService.CreateNotification()
.WithSeverity(NotificationSeverity.Error)
.WithMessage($"Failed to enable plugin {Plugin.Info.Name}\r\n{e.Message}")
.HavingButton(b => b.WithText("View logs").WithCommand(ShowLogsFolder))
.Show());
}
finally
{
Enabling = false;
this.RaisePropertyChanged(nameof(IsEnabled));
}
}
private void CheckPrerequisites()
{
CanInstallPrerequisites = Plugin.Info.PlatformPrerequisites.Any() ||
Plugin.Features.Where(f => f.AlwaysEnabled).Any(f => f.PlatformPrerequisites.Any());
CanRemovePrerequisites = Plugin.Info.PlatformPrerequisites.Any(p => p.UninstallActions.Any()) ||
Plugin.Features.Where(f => f.AlwaysEnabled).Any(f => f.PlatformPrerequisites.Any(p => p.UninstallActions.Any()));
}
private void OnPluginToggled(object? sender, EventArgs e)
{
Dispatcher.UIThread.Post(() =>
{
this.RaisePropertyChanged(nameof(IsEnabled));
if (!IsEnabled)
_window?.Close();
});
}
}

View File

@ -9,6 +9,7 @@
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs.LayerHintsDialogView"
x:DataType="dialogs:LayerHintsDialogViewModel"
WindowStartupLocation="CenterOwner"
Icon="/Assets/Images/Logo/application.ico"
Title="Artemis | Adaption hints"
Width="750"
Height="800">

View File

@ -7,6 +7,7 @@
d:DesignWidth="800"
d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Windows.BrushConfigurationWindowView"
Icon="/Assets/Images/Logo/application.ico"
Title="Artemis | Brush configuration"
Width="{Binding Configuration.DialogWidth}"
Height="{Binding Configuration.DialogHeight}"

View File

@ -7,6 +7,7 @@
d:DesignWidth="800"
d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Windows.EffectConfigurationWindowView"
Icon="/Assets/Images/Logo/application.ico"
Title="Artemis | Effect configuration"
Width="{Binding Configuration.DialogWidth}"
Height="{Binding Configuration.DialogHeight}"

View File

@ -9,6 +9,7 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Scripting.ScriptsDialogView"
x:DataType="scripting:ScriptsDialogViewModel"
Icon="/Assets/Images/Logo/application.ico"
Title="Artemis | Scripts"
Width="1200"
Height="750">

View File

@ -10,8 +10,10 @@ using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.LayerBrushes;
using Artemis.Core.Services;
using Artemis.UI.Screens.StartupWizard;
using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Avalonia;
using DynamicData;
using FluentAvalonia.Styling;
@ -26,12 +28,14 @@ namespace Artemis.UI.Screens.Settings
private readonly PluginSetting<LayerBrushReference> _defaultLayerBrushDescriptor;
private readonly ISettingsService _settingsService;
private readonly IDebugService _debugService;
private readonly IWindowService _windowService;
public GeneralTabViewModel(IKernel kernel, ISettingsService settingsService, IPluginManagementService pluginManagementService, IDebugService debugService)
public GeneralTabViewModel(IKernel kernel, ISettingsService settingsService, IPluginManagementService pluginManagementService, IDebugService debugService, IWindowService windowService)
{
DisplayName = "General";
_settingsService = settingsService;
_debugService = debugService;
_windowService = windowService;
List<LayerBrushProvider> layerBrushProviders = pluginManagementService.GetFeaturesOfType<LayerBrushProvider>();
LayerBrushDescriptors = new ObservableCollection<LayerBrushDescriptor>(layerBrushProviders.SelectMany(l => l.LayerBrushDescriptors));
@ -48,7 +52,7 @@ namespace Artemis.UI.Screens.Settings
ShowLogs = ReactiveCommand.Create(ExecuteShowLogs);
CheckForUpdate = ReactiveCommand.CreateFromTask(ExecuteCheckForUpdate);
ShowSetupWizard = ReactiveCommand.Create(ExecuteShowSetupWizard);
ShowSetupWizard = ReactiveCommand.CreateFromTask(ExecuteShowSetupWizard);
ShowDebugger = ReactiveCommand.Create(ExecuteShowDebugger);
ShowDataFolder = ReactiveCommand.Create(ExecuteShowDataFolder);
}
@ -139,8 +143,9 @@ namespace Artemis.UI.Screens.Settings
#region Tools
private void ExecuteShowSetupWizard()
private async Task ExecuteShowSetupWizard()
{
await _windowService.ShowDialogAsync<StartupWizardViewModel, bool>();
}
private void ExecuteShowDebugger()
@ -164,11 +169,4 @@ namespace Artemis.UI.Screens.Settings
}
}
}
public enum ApplicationColorScheme
{
Light,
Dark,
Automatic
}
}

View File

@ -41,7 +41,7 @@ namespace Artemis.UI.Screens.Settings
plugins.Connect()
.Filter(pluginFilter)
.Sort(SortExpressionComparer<Plugin>.Ascending(p => p.Info.Name))
.TransformAsync(p => Dispatcher.UIThread.InvokeAsync(() => settingsVmFactory.CreatePluginSettingsViewModel(p), DispatcherPriority.Background))
.TransformAsync(p => Dispatcher.UIThread.InvokeAsync(() => settingsVmFactory.PluginSettingsViewModel(p), DispatcherPriority.Background))
.Bind(out ReadOnlyObservableCollection<PluginSettingsViewModel> pluginViewModels)
.Subscribe();
Plugins = pluginViewModels;
@ -88,9 +88,9 @@ namespace Artemis.UI.Screens.Settings
// Wait for the VM to be created asynchronously (it would be better to respond to some event here)
await Task.Delay(200);
// Enable it via the VM to enable the prerequisite dialog
PluginSettingsViewModel? pluginViewModel = Plugins.FirstOrDefault(i => i.Plugin == plugin);
if (pluginViewModel is {IsEnabled: false})
await pluginViewModel.UpdateEnabled(true);
PluginSettingsViewModel? settingsViewModel = Plugins.FirstOrDefault(i => i.PluginViewModel.Plugin == plugin);
if (settingsViewModel != null && !settingsViewModel.PluginViewModel.IsEnabled)
await settingsViewModel.PluginViewModel.UpdateEnabled(true);
_notificationService.CreateNotification()
.WithTitle("Plugin imported")

View File

@ -11,8 +11,8 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="850"
x:Class="Artemis.UI.Screens.Sidebar.ProfileConfigurationEditView"
x:DataType="local:ProfileConfigurationEditViewModel"
Title="{CompiledBinding DisplayName}"
Icon="/Assets/Images/Logo/application.ico"
Title="{CompiledBinding DisplayName}"
Width="800"
MinWidth="420"
Height="975">

View File

@ -0,0 +1,26 @@
<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"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:startupWizard="clr-namespace:Artemis.UI.Screens.StartupWizard"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.StartupWizard.StartupWizardView"
x:DataType="startupWizard:StartupWizardViewModel"
Icon="/Assets/Images/Logo/application.ico"
Title="Artemis | Startup wizard"
Width="1000"
Height="735"
WindowStartupLocation="CenterOwner">
<Grid Margin="15" RowDefinitions="*,Auto" ColumnDefinitions="Auto,*">
<controls:Frame Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Name="Frame" />
<Button Grid.Row="1" Grid.Column="0" Command="{CompiledBinding SkipOrFinishWizard}" IsVisible="{CompiledBinding !ShowFinish}">Skip &amp; close</Button>
<StackPanel Grid.Row="1" Grid.Column="1" HorizontalAlignment="Right" Orientation="Horizontal" Spacing="5" Margin="0 15 0 0">
<Button Command="{CompiledBinding GoBack}" IsVisible="{CompiledBinding ShowGoBack}">Back</Button>
<Button Command="{CompiledBinding Continue}" IsVisible="{CompiledBinding ShowContinue}" Width="80">Continue</Button>
<Button Command="{CompiledBinding SkipOrFinishWizard}" IsVisible="{CompiledBinding ShowFinish}" Width="80">Finish</Button>
</StackPanel>
</Grid>
</Window>

View File

@ -0,0 +1,48 @@
using System;
using System.Reactive.Disposables;
using Artemis.UI.Screens.StartupWizard.Steps;
using Artemis.UI.Shared;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Navigation;
using ReactiveUI;
namespace Artemis.UI.Screens.StartupWizard;
public class StartupWizardView : ReactiveCoreWindow<StartupWizardViewModel>
{
private readonly Frame _frame;
public StartupWizardView()
{
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
_frame = this.Get<Frame>("Frame");
this.WhenActivated(d => ViewModel.WhenAnyValue(vm => vm.CurrentStep).Subscribe(ApplyCurrentStep).DisposeWith(d));
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void ApplyCurrentStep(int step)
{
if (step == 1)
_frame.NavigateToType(typeof(WelcomeStep), null, new FrameNavigationOptions());
else if (step == 2)
_frame.NavigateToType(typeof(DevicesStep), null, new FrameNavigationOptions());
else if (step == 3)
_frame.NavigateToType(typeof(LayoutStep), null, new FrameNavigationOptions());
else if (step == 4)
_frame.NavigateToType(typeof(SettingsStep), null, new FrameNavigationOptions());
else if (step == 5)
_frame.NavigateToType(typeof(FinishStep), null, new FrameNavigationOptions());
}
}

View File

@ -0,0 +1,126 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using System.Reflection;
using Artemis.Core;
using Artemis.Core.DeviceProviders;
using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.Plugins;
using Artemis.UI.Shared;
using ReactiveUI;
namespace Artemis.UI.Screens.StartupWizard;
public class StartupWizardViewModel : DialogViewModelBase<bool>
{
private readonly IRgbService _rgbService;
private readonly ISettingsService _settingsService;
private int _currentStep;
private bool _showContinue;
private bool _showGoBack;
private bool _showFinish;
public StartupWizardViewModel(ISettingsService settingsService, IRgbService rgbService, IPluginManagementService pluginManagementService, ISettingsVmFactory settingsVmFactory)
{
_settingsService = settingsService;
_rgbService = rgbService;
Continue = ReactiveCommand.Create(ExecuteContinue);
GoBack = ReactiveCommand.Create(ExecuteGoBack);
SkipOrFinishWizard = ReactiveCommand.Create(ExecuteSkipOrFinishWizard);
SelectLayout = ReactiveCommand.Create<string>(ExecuteSelectLayout);
AssemblyInformationalVersionAttribute? versionAttribute = typeof(StartupWizardViewModel).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
Version = $"Version {versionAttribute?.InformationalVersion} build {Constants.BuildInfo.BuildNumberDisplay}";
// Take all compatible plugins that have an always-enabled device provider
DeviceProviders = new ObservableCollection<PluginViewModel>(pluginManagementService.GetAllPlugins()
.Where(p => p.Info.IsCompatible && p.Features.Any(f => f.AlwaysEnabled && f.FeatureType.IsAssignableTo(typeof(DeviceProvider))))
.OrderBy(p => p.Info.Name)
.Select(p => settingsVmFactory.PluginViewModel(p, null)));
CurrentStep = 1;
SetupButtons();
}
public ReactiveCommand<Unit, Unit> Continue { get; }
public ReactiveCommand<Unit, Unit> GoBack { get; }
public ReactiveCommand<Unit, Unit> SkipOrFinishWizard { get; }
public ReactiveCommand<string, Unit> SelectLayout { get; }
public string Version { get; }
public ObservableCollection<PluginViewModel> DeviceProviders { get; }
public PluginSetting<bool> UIAutoRun => _settingsService.GetSetting("UI.AutoRun", false);
public PluginSetting<int> UIAutoRunDelay => _settingsService.GetSetting("UI.AutoRunDelay", 15);
public PluginSetting<bool> UIShowOnStartup => _settingsService.GetSetting("UI.ShowOnStartup", true);
public PluginSetting<bool> UICheckForUpdates => _settingsService.GetSetting("UI.CheckForUpdates", true);
public int CurrentStep
{
get => _currentStep;
set => RaiseAndSetIfChanged(ref _currentStep, value);
}
public bool ShowContinue
{
get => _showContinue;
set => RaiseAndSetIfChanged(ref _showContinue, value);
}
public bool ShowGoBack
{
get => _showGoBack;
set => RaiseAndSetIfChanged(ref _showGoBack, value);
}
public bool ShowFinish
{
get => _showFinish;
set => RaiseAndSetIfChanged(ref _showFinish, value);
}
private void ExecuteGoBack()
{
if (CurrentStep > 1)
CurrentStep--;
SetupButtons();
}
private void ExecuteContinue()
{
if (CurrentStep < 5)
CurrentStep++;
SetupButtons();
}
private void SetupButtons()
{
ShowContinue = CurrentStep != 3 && CurrentStep < 5;
ShowGoBack = CurrentStep > 1;
ShowFinish = CurrentStep == 5;
}
private void ExecuteSkipOrFinishWizard()
{
PluginSetting<bool> setting = _settingsService.GetSetting("UI.SetupWizardCompleted", false);
setting.Value = true;
setting.Save();
Close(true);
}
private void ExecuteSelectLayout(string layout)
{
// TODO: Implement the layout
_rgbService.AutoArrangeDevices();
ExecuteContinue();
}
}

View File

@ -0,0 +1,44 @@
<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"
xmlns:startupWizard="clr-namespace:Artemis.UI.Screens.StartupWizard"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.StartupWizard.Steps.DevicesStep"
x:DataType="startupWizard:StartupWizardViewModel">
<Border Classes="card">
<Grid RowDefinitions="Auto,*,Auto,Auto">
<StackPanel Grid.Row="0">
<TextBlock TextWrapping="Wrap">
Devices are supported through the use of device providers.
</TextBlock>
<TextBlock TextWrapping="Wrap">
In the list below you can enable device providers for each brand you own by checking "Enable feature".
</TextBlock>
</StackPanel>
<ScrollViewer Grid.Row="1" Margin="0 15">
<ItemsControl Items="{CompiledBinding DeviceProviders}" Margin="-4 0 8 0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="2" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Classes="card-condensed" Margin="4">
<ContentControl Content="{Binding}"></ContentControl>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<TextBlock Grid.Row="2" Foreground="#FFB9A40A" TextWrapping="Wrap">
Note: To avoid possible instability it's recommended to disable the device providers of brands you don't own.
</TextBlock>
</Grid>
</Border>
</UserControl>

View File

@ -0,0 +1,18 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Screens.StartupWizard.Steps;
public partial class DevicesStep : UserControl
{
public DevicesStep()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

View File

@ -0,0 +1,52 @@
<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"
xmlns:startupWizard="clr-namespace:Artemis.UI.Screens.StartupWizard"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.StartupWizard.Steps.FinishStep"
x:DataType="startupWizard:StartupWizardViewModel">
<Border Classes="card" VerticalAlignment="Top">
<StackPanel>
<TextBlock Classes="h4">All finished!</TextBlock>
<TextBlock TextWrapping="Wrap">
You are now ready to start using Artemis, enjoy! 😁
</TextBlock>
<TextBlock TextWrapping="Wrap">
To learn more about Artemis and how to use it you may find these resources useful:
</TextBlock>
<Grid ColumnDefinitions="Auto,*" Margin="0 15 0 0">
<Grid.Styles>
<Style Selector="TextBlock.link-name">
<Setter Property="Margin" Value="0 7 15 6" />
<Setter Property="FontWeight" Value="600" />
</Style>
</Grid.Styles>
<StackPanel Grid.Row="0" Grid.Column="0">
<TextBlock Classes="link-name">Artemis wiki</TextBlock>
<TextBlock Classes="link-name">Getting started guide</TextBlock>
<TextBlock Classes="link-name">GitHub</TextBlock>
<TextBlock Classes="link-name">Discord</TextBlock>
</StackPanel>
<StackPanel Grid.Column="1">
<controls:HyperlinkButton NavigateUri="https://wiki.artemis-rgb.com/">
https://wiki.artemis-rgb.com/
</controls:HyperlinkButton>
<controls:HyperlinkButton NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/introduction">
https://wiki.artemis-rgb.com/en/guides/user/introduction
</controls:HyperlinkButton>
<controls:HyperlinkButton NavigateUri="https://github.com/Artemis-RGB/Artemis">
https://github.com/Artemis-RGB/Artemis
</controls:HyperlinkButton>
<controls:HyperlinkButton NavigateUri="https://discord.gg/S3MVaC9">
https://discord.gg/S3MVaC9
</controls:HyperlinkButton>
</StackPanel>
</Grid>
</StackPanel>
</Border>
</UserControl>

View File

@ -0,0 +1,18 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Screens.StartupWizard.Steps;
public partial class FinishStep : UserControl
{
public FinishStep()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

View File

@ -0,0 +1,63 @@
<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"
xmlns:startupWizard="clr-namespace:Artemis.UI.Screens.StartupWizard"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.StartupWizard.Steps.LayoutStep"
x:DataType="startupWizard:StartupWizardViewModel">
<Grid RowDefinitions="Auto,*,Auto,Auto" ColumnDefinitions="*,*">
<Border Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Classes="card">
<StackPanel>
<TextBlock TextWrapping="Wrap">
Artemis uses "spatial awareness" to create realistic effects across multiple devices.
</TextBlock>
<TextBlock TextWrapping="Wrap">
In order to do this correctly, we need to know where your devices are located on your desk. Select one of the two presets below, after the setup wizard finishes you can tweak this in detail in the surface editor.
</TextBlock>
</StackPanel>
</Border>
<Button Grid.Row="1"
Grid.Column="0"
Command="{CompiledBinding SelectLayout}"
CommandParameter="left"
HorizontalAlignment="Right"
Margin="0 0 10 0"
Width="280"
Height="280"
IsEnabled="False">
<StackPanel>
<avalonia:MaterialIcon Kind="HandLeft" Width="150" Height="150" HorizontalAlignment="Center" />
<TextBlock TextAlignment="Center" Classes="h4" Margin="0 10 0 0">
Left-handed preset (NYI)
</TextBlock>
<TextBlock TextAlignment="Center" Classes="subtitle" TextWrapping="Wrap">
A preset with the mouse on the left side of the keyboard
</TextBlock>
</StackPanel>
</Button>
<Button Grid.Row="1"
Grid.Column="1"
Command="{CompiledBinding SelectLayout}"
CommandParameter="right"
HorizontalAlignment="Left"
Margin="10 0 0 0"
Width="280"
Height="280">
<StackPanel>
<avalonia:MaterialIcon Kind="HandRight" Width="150" Height="150" HorizontalAlignment="Center" />
<TextBlock TextAlignment="Center" Classes="h4" Margin="0 10 0 0">
Right-handed preset
</TextBlock>
<TextBlock TextAlignment="Center" Classes="subtitle" TextWrapping="Wrap">
A preset with the mouse on the right side of the keyboard
</TextBlock>
</StackPanel>
</Button>
</Grid>
</UserControl>

View File

@ -0,0 +1,18 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Screens.StartupWizard.Steps;
public partial class LayoutStep : UserControl
{
public LayoutStep()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

View File

@ -0,0 +1,74 @@
<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"
xmlns:startupWizard="clr-namespace:Artemis.UI.Screens.StartupWizard"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.StartupWizard.Steps.SettingsStep"
x:DataType="startupWizard:StartupWizardViewModel">
<Border Classes="card" VerticalAlignment="Top">
<StackPanel>
<TextBlock>
Artemis comes with a variety of settings you can change to tweak everything to your liking.
</TextBlock>
<TextBlock>
Below you can find a few relevant settings, many more can be changed later on the settings page.
</TextBlock>
<Border Classes="card" Margin="0 15 0 0">
<StackPanel>
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>Auto-run on startup</TextBlock>
</StackPanel>
<ToggleSwitch Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" IsChecked="{CompiledBinding UIAutoRun.Value}" MinWidth="0" Margin="0 -10" />
</Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>Hide window on auto-run</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleSwitch IsChecked="{CompiledBinding !UIShowOnStartup.Value}" IsEnabled="{CompiledBinding UIAutoRun.Value}" MinWidth="0" Margin="0 -10" />
</StackPanel>
</Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>Startup delay</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
Set the amount of seconds to wait before auto-running Artemis.
</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
If some devices don't work because Artemis starts before the manufacturer's software, try increasing this value.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" Orientation="Horizontal">
<TextBox Text="{CompiledBinding UIAutoRunDelay.Value}" IsEnabled="{CompiledBinding UIAutoRun.Value}" Width="120" />
<TextBlock VerticalAlignment="Center" TextAlignment="Right" Width="30">sec</TextBlock>
</StackPanel>
</Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>
Check for updates
</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
If enabled, we'll check for updates on startup and periodically while running.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleSwitch IsChecked="{CompiledBinding UICheckForUpdates.Value}" MinWidth="0" />
</StackPanel>
</Grid>
</StackPanel>
</Border>
</StackPanel>
</Border>
</UserControl>

View File

@ -0,0 +1,18 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Screens.StartupWizard.Steps;
public partial class SettingsStep : UserControl
{
public SettingsStep()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

View File

@ -0,0 +1,61 @@
<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"
xmlns:startupWizard="clr-namespace:Artemis.UI.Screens.StartupWizard"
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="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.StartupWizard.Steps.WelcomeStep"
x:DataType="startupWizard:StartupWizardViewModel">
<StackPanel>
<Grid RowDefinitions="*,*" ColumnDefinitions="Auto,*,Auto">
<Image Grid.Column="0" Grid.RowSpan="2" Width="65" Height="65" VerticalAlignment="Center" Source="/Assets/Images/Logo/bow.png" Margin="0 0 20 0" />
<TextBlock Grid.Row="0" Grid.Column="1" FontSize="36" VerticalAlignment="Bottom">
Artemis 2
</TextBlock>
<StackPanel Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Bottom" Orientation="Horizontal">
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View website" NavigateUri="https://artemis-rgb.com">
<avalonia:MaterialIcon Kind="Web" />
</controls:HyperlinkButton>
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View GitHub repository" NavigateUri="https://github.com/Artemis-RGB/Artemis">
<avalonia:MaterialIcon Kind="Github" />
</controls:HyperlinkButton>
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View Wiki" NavigateUri="https://wiki.artemis-rgb.com">
<avalonia:MaterialIcon Kind="BookOpenOutline" />
</controls:HyperlinkButton>
</StackPanel>
<TextBlock Grid.Row="1"
Grid.Column="1"
VerticalAlignment="Top"
Foreground="{DynamicResource MaterialDesignBodyLight}"
Text="{Binding Version}" />
<controls:HyperlinkButton Grid.Row="1"
Grid.Column="2"
VerticalAlignment="Top"
NavigateUri="https://github.com/Artemis-RGB/Artemis/blob/master/LICENSE">
PolyForm Noncommercial License 1.0.0
</controls:HyperlinkButton>
</Grid>
<Border Classes="card">
<StackPanel>
<TextBlock Classes="h4">Welcome to the Artemis startup wizard!</TextBlock>
<TextBlock TextWrapping="Wrap">
In this wizard we'll walk you through the initial configuration of Artemis.
</TextBlock>
<TextBlock TextWrapping="Wrap">
Before you can start you need to tell Artemis which devices you want to use and where they are placed on your desk.
</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap" Margin="0 15 0 0">
PS: You can also skip the wizard and set things up yourself.
</TextBlock>
</StackPanel>
</Border>
</StackPanel>
</UserControl>

View File

@ -0,0 +1,18 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Screens.StartupWizard.Steps;
public partial class WelcomeStep : UserControl
{
public WelcomeStep()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

View File

@ -9,6 +9,7 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.VisualScripting.NodeScriptWindowView"
x:DataType="visualScripting:NodeScriptWindowViewModel"
Icon="/Assets/Images/Logo/application.ico"
Title="Artemis | Visual Script Editor"
Width="1200"
Height="800">