Code style - Use file scoped namespaces

Code style - Ran code cleanup
This commit is contained in:
Robert committed 2022-08-21 11:36:15 +02:00
1 parent e68e16df4d
commit f6090dc296
691 files changed
+37301 -38119

No files matched your search

@@ -4,7 +4,6 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:reactiveUi="http://reactiveui.net"
xmlns:debugger="clr-namespace:Artemis.UI.Screens.Debugger"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="800"
@@ -6,39 +6,35 @@ using Artemis.UI.Shared.Events;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using FluentAvalonia.Core;
using FluentAvalonia.UI.Controls;
using ReactiveUI;
namespace Artemis.UI.Screens.Debugger
namespace Artemis.UI.Screens.Debugger;
public class DebugView : ReactiveCoreWindow<DebugViewModel>
{
public class DebugView : ReactiveCoreWindow<DebugViewModel>
public DebugView()
{
public DebugView()
{
InitializeComponent();
InitializeComponent();
#if DEBUG
this.AttachDevTools();
this.AttachDevTools();
#endif
this.WhenActivated(d =>
this.WhenActivated(d =>
{
Observable.FromEventPattern(x => ViewModel!.ActivationRequested += x, x => ViewModel!.ActivationRequested -= x).Subscribe(_ =>
{
Observable.FromEventPattern(x => ViewModel!.ActivationRequested += x, x => ViewModel!.ActivationRequested -= x).Subscribe(_ =>
{
WindowState = WindowState.Normal;
Activate();
}).DisposeWith(d);
});
}
WindowState = WindowState.Normal;
Activate();
}).DisposeWith(d);
});
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void DeviceVisualizer_OnLedClicked(object? sender, LedClickedEventArgs e)
{
}
private void DeviceVisualizer_OnLedClicked(object? sender, LedClickedEventArgs e)
{
}
}
@@ -29,7 +29,7 @@ public class PerformanceDebugViewModel : ActivatableViewModelBase
_updateTimer = new DispatcherTimer(TimeSpan.FromMilliseconds(500), DispatcherPriority.Normal, (_, _) => Update());
DisplayName = "Performance";
this.WhenActivated(disposables =>
{
Observable.FromEventPattern<PluginFeatureEventArgs>(x => pluginManagementService.PluginFeatureEnabled += x, x => pluginManagementService.PluginFeatureEnabled -= x)
@@ -1,18 +1,17 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Device
{
public class DeviceDetectInputView : ReactiveUserControl<DeviceDetectInputViewModel>
{
public DeviceDetectInputView()
{
InitializeComponent();
}
namespace Artemis.UI.Screens.Device;
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public class DeviceDetectInputView : ReactiveUserControl<DeviceDetectInputViewModel>
{
public DeviceDetectInputView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
@@ -11,57 +11,56 @@ using FluentAvalonia.UI.Controls;
using ReactiveUI;
using RGB.NET.Core;
namespace Artemis.UI.Screens.Device
namespace Artemis.UI.Screens.Device;
public class DeviceDetectInputViewModel : ContentDialogViewModelBase
{
public class DeviceDetectInputViewModel : ContentDialogViewModelBase
private readonly IInputService _inputService;
private readonly ListLedGroup _ledGroup;
private readonly INotificationService _notificationService;
private readonly IRgbService _rgbService;
public DeviceDetectInputViewModel(ArtemisDevice device, IInputService inputService, INotificationService notificationService, IRgbService rgbService)
{
private readonly IInputService _inputService;
private readonly INotificationService _notificationService;
private readonly IRgbService _rgbService;
private readonly ListLedGroup _ledGroup;
_inputService = inputService;
_notificationService = notificationService;
_rgbService = rgbService;
public DeviceDetectInputViewModel(ArtemisDevice device, IInputService inputService, INotificationService notificationService, IRgbService rgbService)
Device = device;
// Create a LED group way at the top
_ledGroup = new ListLedGroup(_rgbService.Surface, Device.Leds.Select(l => l.RgbLed))
{
_inputService = inputService;
_notificationService = notificationService;
_rgbService = rgbService;
Brush = new SolidColorBrush(new Color(255, 255, 0)),
ZIndex = 999
};
Device = device;
// Create a LED group way at the top
_ledGroup = new ListLedGroup(_rgbService.Surface, Device.Leds.Select(l => l.RgbLed))
{
Brush = new SolidColorBrush(new Color(255, 255, 0)),
ZIndex = 999
};
this.WhenActivated(disposables =>
{
_inputService.IdentifyDevice(device);
Observable.FromEventPattern(x => _inputService.DeviceIdentified += x, x => _inputService.DeviceIdentified -= x)
.Subscribe(_ => InputServiceOnDeviceIdentified())
.DisposeWith(disposables);
Disposable.Create(() =>
{
_inputService.StopIdentify();
_ledGroup.Detach();
}).DisposeWith(disposables);
});
}
public ArtemisDevice Device { get; }
public bool IsMouse => Device.RgbDevice.DeviceInfo.DeviceType == RGBDeviceType.Mouse;
public bool MadeChanges { get; set; }
private void InputServiceOnDeviceIdentified()
this.WhenActivated(disposables =>
{
ContentDialog?.Hide(ContentDialogResult.Primary);
_notificationService.CreateNotification()
.WithMessage($"{Device.RgbDevice.DeviceInfo.DeviceName} identified 😁")
.WithSeverity(NotificationSeverity.Success)
.Show();
}
_inputService.IdentifyDevice(device);
Observable.FromEventPattern(x => _inputService.DeviceIdentified += x, x => _inputService.DeviceIdentified -= x)
.Subscribe(_ => InputServiceOnDeviceIdentified())
.DisposeWith(disposables);
Disposable.Create(() =>
{
_inputService.StopIdentify();
_ledGroup.Detach();
}).DisposeWith(disposables);
});
}
public ArtemisDevice Device { get; }
public bool IsMouse => Device.RgbDevice.DeviceInfo.DeviceType == RGBDeviceType.Mouse;
public bool MadeChanges { get; set; }
private void InputServiceOnDeviceIdentified()
{
ContentDialog?.Hide(ContentDialogResult.Primary);
_notificationService.CreateNotification()
.WithMessage($"{Device.RgbDevice.DeviceInfo.DeviceName} identified 😁")
.WithSeverity(NotificationSeverity.Success)
.Show();
}
}
@@ -1,5 +1,4 @@
using System;
using System.Collections.ObjectModel;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
@@ -10,53 +9,51 @@ using Artemis.UI.Shared;
using ReactiveUI;
using RGB.NET.Core;
using SkiaSharp;
using ArtemisLed = Artemis.Core.ArtemisLed;
namespace Artemis.UI.Screens.Device
namespace Artemis.UI.Screens.Device;
public class DevicePropertiesViewModel : DialogViewModelBase<object>
{
public class DevicePropertiesViewModel : DialogViewModelBase<object>
public DevicePropertiesViewModel(ArtemisDevice device, ICoreService coreService, IDeviceVmFactory deviceVmFactory)
{
public DevicePropertiesViewModel(ArtemisDevice device, ICoreService coreService, IDeviceVmFactory deviceVmFactory)
Device = device;
SelectedLeds = new ObservableCollection<ArtemisLed>();
Tabs = new ObservableCollection<ActivatableViewModelBase>();
Tabs.Add(deviceVmFactory.DevicePropertiesTabViewModel(device));
Tabs.Add(deviceVmFactory.DeviceInfoTabViewModel(device));
if (Device.DeviceType == RGBDeviceType.Keyboard)
Tabs.Add(deviceVmFactory.InputMappingsTabViewModel(device, SelectedLeds));
Tabs.Add(deviceVmFactory.DeviceLedsTabViewModel(device, SelectedLeds));
this.WhenActivated(d =>
{
Device = device;
SelectedLeds = new ObservableCollection<ArtemisLed>();
Tabs = new ObservableCollection<ActivatableViewModelBase>();
coreService.FrameRendering += CoreServiceOnFrameRendering;
Disposable.Create(() => coreService.FrameRendering -= CoreServiceOnFrameRendering).DisposeWith(d);
});
Tabs.Add(deviceVmFactory.DevicePropertiesTabViewModel(device));
Tabs.Add(deviceVmFactory.DeviceInfoTabViewModel(device));
if (Device.DeviceType == RGBDeviceType.Keyboard)
Tabs.Add(deviceVmFactory.InputMappingsTabViewModel(device, SelectedLeds));
Tabs.Add(deviceVmFactory.DeviceLedsTabViewModel(device, SelectedLeds));
ClearSelectedLeds = ReactiveCommand.Create(ExecuteClearSelectedLeds);
}
this.WhenActivated(d =>
{
coreService.FrameRendering += CoreServiceOnFrameRendering;
Disposable.Create(() => coreService.FrameRendering -= CoreServiceOnFrameRendering).DisposeWith(d);
});
public ArtemisDevice Device { get; }
public ObservableCollection<ArtemisLed> SelectedLeds { get; }
public ObservableCollection<ActivatableViewModelBase> Tabs { get; }
ClearSelectedLeds = ReactiveCommand.Create(ExecuteClearSelectedLeds);
}
public ReactiveCommand<Unit, Unit> ClearSelectedLeds { get; }
public ArtemisDevice Device { get; }
public ObservableCollection<ArtemisLed> SelectedLeds { get; }
public ObservableCollection<ActivatableViewModelBase> Tabs { get; }
private void ExecuteClearSelectedLeds()
{
SelectedLeds.Clear();
}
public ReactiveCommand<Unit, Unit> ClearSelectedLeds { get; }
private void ExecuteClearSelectedLeds()
{
SelectedLeds.Clear();
}
private void CoreServiceOnFrameRendering(object? sender, FrameRenderingEventArgs e)
{
if (!SelectedLeds.Any())
return;
private void CoreServiceOnFrameRendering(object? sender, FrameRenderingEventArgs e)
{
if (!SelectedLeds.Any())
return;
using SKPaint highlightPaint = new() {Color = SKColors.White};
using SKPaint dimPaint = new() {Color = new SKColor(0, 0, 0, 192)};
foreach (ArtemisLed artemisLed in Device.Leds)
e.Canvas.DrawRect(artemisLed.AbsoluteRectangle, SelectedLeds.Contains(artemisLed) ? highlightPaint : dimPaint);
}
using SKPaint highlightPaint = new() {Color = SKColors.White};
using SKPaint dimPaint = new() {Color = new SKColor(0, 0, 0, 192)};
foreach (ArtemisLed artemisLed in Device.Leds)
e.Canvas.DrawRect(artemisLed.AbsoluteRectangle, SelectedLeds.Contains(artemisLed) ? highlightPaint : dimPaint);
}
}
@@ -1,19 +1,17 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Device
{
public partial class DeviceSettingsView : ReactiveUserControl<DeviceSettingsViewModel>
{
public DeviceSettingsView()
{
InitializeComponent();
}
namespace Artemis.UI.Screens.Device;
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public class DeviceSettingsView : ReactiveUserControl<DeviceSettingsViewModel>
{
public DeviceSettingsView()
{
InitializeComponent();
}
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
@@ -11,114 +11,113 @@ using Humanizer;
using ReactiveUI;
using RGB.NET.Core;
namespace Artemis.UI.Screens.Device
namespace Artemis.UI.Screens.Device;
public class DeviceSettingsViewModel : ActivatableViewModelBase
{
public class DeviceSettingsViewModel : ActivatableViewModelBase
private readonly IDeviceService _deviceService;
private readonly DevicesTabViewModel _devicesTabViewModel;
private readonly IDeviceVmFactory _deviceVmFactory;
private readonly IRgbService _rgbService;
private readonly IWindowService _windowService;
private bool _togglingDevice;
public DeviceSettingsViewModel(ArtemisDevice device, DevicesTabViewModel devicesTabViewModel, IDeviceService deviceService, IWindowService windowService, IDeviceVmFactory deviceVmFactory,
IRgbService rgbService)
{
private readonly IDeviceService _deviceService;
private readonly DevicesTabViewModel _devicesTabViewModel;
private readonly IDeviceVmFactory _deviceVmFactory;
private readonly IRgbService _rgbService;
private readonly IWindowService _windowService;
private bool _togglingDevice;
_devicesTabViewModel = devicesTabViewModel;
_deviceService = deviceService;
_windowService = windowService;
_deviceVmFactory = deviceVmFactory;
_rgbService = rgbService;
Device = device;
public DeviceSettingsViewModel(ArtemisDevice device, DevicesTabViewModel devicesTabViewModel, IDeviceService deviceService, IWindowService windowService, IDeviceVmFactory deviceVmFactory,
IRgbService rgbService)
{
_devicesTabViewModel = devicesTabViewModel;
_deviceService = deviceService;
_windowService = windowService;
_deviceVmFactory = deviceVmFactory;
_rgbService = rgbService;
Device = device;
Type = Device.DeviceType.ToString().Humanize();
Name = Device.RgbDevice.DeviceInfo.Model;
Manufacturer = Device.RgbDevice.DeviceInfo.Manufacturer;
Type = Device.DeviceType.ToString().Humanize();
Name = Device.RgbDevice.DeviceInfo.Model;
Manufacturer = Device.RgbDevice.DeviceInfo.Manufacturer;
DetectInput = ReactiveCommand.CreateFromTask(ExecuteDetectInput, this.WhenAnyValue(vm => vm.CanDetectInput));
}
DetectInput = ReactiveCommand.CreateFromTask(ExecuteDetectInput, this.WhenAnyValue(vm => vm.CanDetectInput));
}
public ArtemisDevice Device { get; }
public ArtemisDevice Device { get; }
public string Type { get; }
public string Name { get; }
public string Manufacturer { get; }
public string Type { get; }
public string Name { get; }
public string Manufacturer { get; }
public bool CanDetectInput => Device.DeviceType is RGBDeviceType.Keyboard or RGBDeviceType.Mouse;
public ReactiveCommand<Unit, Unit> DetectInput { get; }
public bool CanDetectInput => Device.DeviceType is RGBDeviceType.Keyboard or RGBDeviceType.Mouse;
public ReactiveCommand<Unit, Unit> DetectInput { get; }
public bool IsDeviceEnabled
{
get => Device.IsEnabled;
set => Dispatcher.UIThread.InvokeAsync(async () => await UpdateIsDeviceEnabled(value));
}
public bool IsDeviceEnabled
{
get => Device.IsEnabled;
set => Dispatcher.UIThread.InvokeAsync(async () => await UpdateIsDeviceEnabled(value));
}
public bool TogglingDevice
{
get => _togglingDevice;
set => RaiseAndSetIfChanged(ref _togglingDevice, value);
}
public bool TogglingDevice
{
get => _togglingDevice;
set => RaiseAndSetIfChanged(ref _togglingDevice, value);
}
public void IdentifyDevice()
{
_deviceService.IdentifyDevice(Device);
}
public void IdentifyDevice()
{
_deviceService.IdentifyDevice(Device);
}
public void OpenPluginDirectory()
{
Utilities.OpenFolder(Device.DeviceProvider.Plugin.Directory.FullName);
}
public void OpenPluginDirectory()
{
Utilities.OpenFolder(Device.DeviceProvider.Plugin.Directory.FullName);
}
public async Task ViewProperties()
{
await _windowService.ShowDialogAsync(_deviceVmFactory.DevicePropertiesViewModel(Device));
}
private async Task ExecuteDetectInput()
{
if (!CanDetectInput)
return;
private async Task ExecuteDetectInput()
{
if (!CanDetectInput)
return;
await _windowService.CreateContentDialog()
.WithTitle($"{Device.RgbDevice.DeviceInfo.DeviceName} - Detect input")
.WithViewModel<DeviceDetectInputViewModel>(out DeviceDetectInputViewModel? viewModel, ("device", Device))
.WithCloseButtonText("Cancel")
.ShowAsync();
await _windowService.CreateContentDialog()
.WithTitle($"{Device.RgbDevice.DeviceInfo.DeviceName} - Detect input")
.WithViewModel<DeviceDetectInputViewModel>(out DeviceDetectInputViewModel? viewModel, ("device", Device))
.WithCloseButtonText("Cancel")
.ShowAsync();
if (viewModel.MadeChanges)
_rgbService.SaveDevice(Device);
}
public async Task ViewProperties()
{
await _windowService.ShowDialogAsync(_deviceVmFactory.DevicePropertiesViewModel(Device));
}
private async Task UpdateIsDeviceEnabled(bool value)
{
if (TogglingDevice)
return;
try
{
TogglingDevice = true;
if (!value)
value = !await _devicesTabViewModel.ShowDeviceDisableDialog();
if (value)
_rgbService.EnableDevice(Device);
else
_rgbService.DisableDevice(Device);
this.RaisePropertyChanged(nameof(IsDeviceEnabled));
SaveDevice();
}
finally
{
TogglingDevice = false;
}
}
private void SaveDevice()
{
if (viewModel.MadeChanges)
_rgbService.SaveDevice(Device);
}
private async Task UpdateIsDeviceEnabled(bool value)
{
if (TogglingDevice)
return;
try
{
TogglingDevice = true;
if (!value)
value = !await _devicesTabViewModel.ShowDeviceDisableDialog();
if (value)
_rgbService.EnableDevice(Device);
else
_rgbService.DisableDevice(Device);
this.RaisePropertyChanged(nameof(IsDeviceEnabled));
SaveDevice();
}
finally
{
TogglingDevice = false;
}
}
private void SaveDevice()
{
_rgbService.SaveDevice(Device);
}
}
@@ -1,19 +1,17 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Device
{
public partial class DeviceInfoTabView : ReactiveUserControl<DeviceInfoTabViewModel>
{
public DeviceInfoTabView()
{
InitializeComponent();
}
namespace Artemis.UI.Screens.Device;
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public class DeviceInfoTabView : ReactiveUserControl<DeviceInfoTabViewModel>
{
public DeviceInfoTabView()
{
InitializeComponent();
}
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
@@ -5,34 +5,33 @@ using Artemis.UI.Shared.Services;
using Avalonia;
using RGB.NET.Core;
namespace Artemis.UI.Screens.Device
namespace Artemis.UI.Screens.Device;
public class DeviceInfoTabViewModel : ActivatableViewModelBase
{
public class DeviceInfoTabViewModel : ActivatableViewModelBase
private readonly INotificationService _notificationService;
public DeviceInfoTabViewModel(ArtemisDevice device, INotificationService notificationService)
{
private readonly INotificationService _notificationService;
_notificationService = notificationService;
public DeviceInfoTabViewModel(ArtemisDevice device, INotificationService notificationService)
{
_notificationService = notificationService;
Device = device;
DisplayName = "Info";
Device = device;
DisplayName = "Info";
DefaultLayoutPath = Device.DeviceProvider.LoadLayout(Device).FilePath;
}
DefaultLayoutPath = Device.DeviceProvider.LoadLayout(Device).FilePath;
}
public bool IsKeyboard => Device.DeviceType == RGBDeviceType.Keyboard;
public ArtemisDevice Device { get; }
public bool IsKeyboard => Device.DeviceType == RGBDeviceType.Keyboard;
public ArtemisDevice Device { get; }
public string DefaultLayoutPath { get; }
public string DefaultLayoutPath { get; }
public async Task CopyToClipboard(string content)
{
if (Application.Current?.Clipboard == null)
return;
public async Task CopyToClipboard(string content)
{
if (Application.Current?.Clipboard == null)
return;
await Application.Current.Clipboard.SetTextAsync(content);
_notificationService.CreateNotification().WithMessage("Copied path to clipboard.").Show();
}
await Application.Current.Clipboard.SetTextAsync(content);
_notificationService.CreateNotification().WithMessage("Copied path to clipboard.").Show();
}
}
@@ -1,7 +1,6 @@
using System.Collections.ObjectModel;
using Artemis.Core;
using Artemis.UI.Shared;
using RGB.NET.Core;
namespace Artemis.UI.Screens.Device;
@@ -1,19 +1,17 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Device
{
public partial class DeviceLedsTabView : ReactiveUserControl<DeviceLedsTabViewModel>
{
public DeviceLedsTabView()
{
InitializeComponent();
}
namespace Artemis.UI.Screens.Device;
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public class DeviceLedsTabView : ReactiveUserControl<DeviceLedsTabViewModel>
{
public DeviceLedsTabView()
{
InitializeComponent();
}
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
@@ -8,35 +8,34 @@ using Artemis.UI.Shared;
using DynamicData.Binding;
using ReactiveUI;
namespace Artemis.UI.Screens.Device
namespace Artemis.UI.Screens.Device;
public class DeviceLedsTabViewModel : ActivatableViewModelBase
{
public class DeviceLedsTabViewModel : ActivatableViewModelBase
private readonly ObservableCollection<ArtemisLed> _selectedLeds;
public DeviceLedsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds)
{
private readonly ObservableCollection<ArtemisLed> _selectedLeds;
_selectedLeds = selectedLeds;
public DeviceLedsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds)
{
_selectedLeds = selectedLeds;
Device = device;
DisplayName = "LEDs";
LedViewModels = new ObservableCollection<DeviceLedsTabLedViewModel>(Device.Leds.Select(l => new DeviceLedsTabLedViewModel(l, _selectedLeds)));
Device = device;
DisplayName = "LEDs";
LedViewModels = new ObservableCollection<DeviceLedsTabLedViewModel>(Device.Leds.Select(l => new DeviceLedsTabLedViewModel(l, _selectedLeds)));
this.WhenActivated(disposables => _selectedLeds.ToObservableChangeSet().Subscribe(_ => UpdateSelectedLeds()).DisposeWith(disposables));
}
this.WhenActivated(disposables => _selectedLeds.ToObservableChangeSet().Subscribe(_ => UpdateSelectedLeds()).DisposeWith(disposables));
}
public ArtemisDevice Device { get; }
public ObservableCollection<DeviceLedsTabLedViewModel> LedViewModels { get; }
public ArtemisDevice Device { get; }
public ObservableCollection<DeviceLedsTabLedViewModel> LedViewModels { get; }
private void SelectedLedsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
UpdateSelectedLeds();
}
private void SelectedLedsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
UpdateSelectedLeds();
}
private void UpdateSelectedLeds()
{
foreach (DeviceLedsTabLedViewModel deviceLedsTabLedViewModel in LedViewModels)
deviceLedsTabLedViewModel.Update();
}
private void UpdateSelectedLeds()
{
foreach (DeviceLedsTabLedViewModel deviceLedsTabLedViewModel in LedViewModels)
deviceLedsTabLedViewModel.Update();
}
}
@@ -5,11 +5,10 @@ using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using Avalonia.Threading;
using ReactiveUI;
namespace Artemis.UI.Screens.Device;
public partial class DeviceLogicalLayoutDialogView : ReactiveUserControl<DeviceLogicalLayoutDialogViewModel>
public class DeviceLogicalLayoutDialogView : ReactiveUserControl<DeviceLogicalLayoutDialogViewModel>
{
private readonly AutoCompleteBox _autoCompleteBox;
@@ -19,7 +18,7 @@ public partial class DeviceLogicalLayoutDialogView : ReactiveUserControl<DeviceL
_autoCompleteBox = this.Get<AutoCompleteBox>("RegionsAutoCompleteBox");
_autoCompleteBox.ItemFilter += SearchRegions;
Dispatcher.UIThread.InvokeAsync(DelayedAutoFocus);
}
@@ -9,6 +9,7 @@ using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using FluentAvalonia.UI.Controls;
using ReactiveUI;
using ContentDialogButton = Artemis.UI.Shared.Services.Builders.ContentDialogButton;
namespace Artemis.UI.Screens.Device;
@@ -25,15 +26,15 @@ public class DeviceLogicalLayoutDialogViewModel : ContentDialogViewModelBase
Device = device;
ApplyLogicalLayout = ReactiveCommand.Create(ExecuteApplyLogicalLayout, this.WhenAnyValue(vm => vm.SelectedRegion).Select(r => r != null));
Regions = new ObservableCollection<RegionInfo>(CultureInfo.GetCultures(CultureTypes.SpecificCultures)
.Where(c => c.LCID != LOCALE_INVARIANT &&
c.LCID != LOCALE_NEUTRAL &&
c.LCID != LOCALE_CUSTOM_DEFAULT &&
.Where(c => c.LCID != LOCALE_INVARIANT &&
c.LCID != LOCALE_NEUTRAL &&
c.LCID != LOCALE_CUSTOM_DEFAULT &&
!c.CultureTypes.HasFlag(CultureTypes.UserCustomCulture))
.Select(c => new RegionInfo(c.LCID))
.GroupBy(r => r.EnglishName)
.Select(g => g.First())
.OrderBy(r => r.EnglishName));
// Default to US/international
SelectedRegion = Regions.FirstOrDefault(r => r.TwoLetterISORegionName == "US");
}
@@ -49,6 +50,19 @@ public class DeviceLogicalLayoutDialogViewModel : ContentDialogViewModelBase
set => RaiseAndSetIfChanged(ref _selectedRegion, value);
}
public static async Task<bool> SelectLogicalLayout(IWindowService windowService, ArtemisDevice device)
{
await windowService.CreateContentDialog()
.WithTitle("Select logical layout")
.WithViewModel(out DeviceLogicalLayoutDialogViewModel vm, ("device", device))
.WithCloseButtonText("Cancel")
.WithDefaultButton(ContentDialogButton.Primary)
.HavingPrimaryButton(b => b.WithText("Select").WithCommand(vm.ApplyLogicalLayout))
.ShowAsync();
return vm.Applied;
}
private void ExecuteApplyLogicalLayout()
{
if (SelectedRegion == null)
@@ -58,18 +72,4 @@ public class DeviceLogicalLayoutDialogViewModel : ContentDialogViewModelBase
Applied = true;
ContentDialog?.Hide(ContentDialogResult.Primary);
}
public static async Task<bool> SelectLogicalLayout(IWindowService windowService, ArtemisDevice device)
{
await windowService.CreateContentDialog()
.WithTitle("Select logical layout")
.WithViewModel(out DeviceLogicalLayoutDialogViewModel vm, ("device", device))
.WithCloseButtonText("Cancel")
.WithDefaultButton(Shared.Services.Builders.ContentDialogButton.Primary)
.HavingPrimaryButton(b => b.WithText("Select").WithCommand(vm.ApplyLogicalLayout))
.ShowAsync();
return vm.Applied;
}
}
@@ -1,11 +1,9 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Device;
public partial class DevicePhysicalLayoutDialogView : ReactiveUserControl<DevicePhysicalLayoutDialogViewModel>
public class DevicePhysicalLayoutDialogView : ReactiveUserControl<DevicePhysicalLayoutDialogViewModel>
{
public DevicePhysicalLayoutDialogView()
{
@@ -21,13 +21,6 @@ public class DevicePhysicalLayoutDialogViewModel : ContentDialogViewModelBase
public ReactiveCommand<string, Unit> ApplyPhysicalLayout { get; }
public bool Applied { get; set; }
private void ExecuteApplyPhysicalLayout(string physicalLayout)
{
Device.PhysicalLayout = Enum.Parse<KeyboardLayoutType>(physicalLayout);
Applied = true;
ContentDialog?.Hide(ContentDialogResult.Primary);
}
public static async Task<bool> SelectPhysicalLayout(IWindowService windowService, ArtemisDevice device)
{
await windowService.CreateContentDialog()
@@ -38,4 +31,11 @@ public class DevicePhysicalLayoutDialogViewModel : ContentDialogViewModelBase
return vm.Applied;
}
private void ExecuteApplyPhysicalLayout(string physicalLayout)
{
Device.PhysicalLayout = Enum.Parse<KeyboardLayoutType>(physicalLayout);
Applied = true;
ContentDialog?.Hide(ContentDialogResult.Primary);
}
}
@@ -2,23 +2,22 @@ using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Device
namespace Artemis.UI.Screens.Device;
public class DevicePropertiesTabView : ReactiveUserControl<DevicePropertiesTabViewModel>
{
public partial class DevicePropertiesTabView : ReactiveUserControl<DevicePropertiesTabViewModel>
public DevicePropertiesTabView()
{
public DevicePropertiesTabView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void InputElement_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
{
ViewModel?.BrowseCustomLayout();
}
InitializeComponent();
}
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void InputElement_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
{
ViewModel?.BrowseCustomLayout();
}
}
@@ -11,270 +11,269 @@ using Artemis.UI.Shared.Services.Builders;
using ReactiveUI;
using SkiaSharp;
namespace Artemis.UI.Screens.Device
namespace Artemis.UI.Screens.Device;
public class DevicePropertiesTabViewModel : ActivatableViewModelBase
{
public class DevicePropertiesTabViewModel : ActivatableViewModelBase
private readonly List<DeviceCategory> _categories;
private readonly ICoreService _coreService;
private readonly float _initialBlueScale;
private readonly float _initialGreenScale;
private readonly float _initialRedScale;
private readonly INotificationService _notificationService;
private readonly IRgbService _rgbService;
private readonly IWindowService _windowService;
private float _blueScale;
private SKColor _currentColor;
private bool _displayOnDevices;
private float _greenScale;
private float _redScale;
private int _rotation;
private float _scale;
private int _x;
private int _y;
public DevicePropertiesTabViewModel(ArtemisDevice device, ICoreService coreService, IRgbService rgbService, IWindowService windowService, INotificationService notificationService)
{
private readonly List<DeviceCategory> _categories;
private readonly ICoreService _coreService;
private readonly float _initialBlueScale;
private readonly float _initialGreenScale;
private readonly float _initialRedScale;
private readonly INotificationService _notificationService;
private readonly IRgbService _rgbService;
private readonly IWindowService _windowService;
private float _blueScale;
private SKColor _currentColor;
private bool _displayOnDevices;
private float _greenScale;
private float _redScale;
private int _rotation;
private float _scale;
private int _x;
private int _y;
_coreService = coreService;
_rgbService = rgbService;
_windowService = windowService;
_notificationService = notificationService;
_categories = new List<DeviceCategory>(device.Categories);
public DevicePropertiesTabViewModel(ArtemisDevice device, ICoreService coreService, IRgbService rgbService, IWindowService windowService, INotificationService notificationService)
Device = device;
DisplayName = "Properties";
X = (int) Device.X;
Y = (int) Device.Y;
Scale = Device.Scale;
Rotation = (int) Device.Rotation;
RedScale = Device.RedScale * 100f;
GreenScale = Device.GreenScale * 100f;
BlueScale = Device.BlueScale * 100f;
CurrentColor = SKColors.White;
// We need to store the initial values to be able to restore them when the user clicks "Cancel"
_initialRedScale = Device.RedScale;
_initialGreenScale = Device.GreenScale;
_initialBlueScale = Device.BlueScale;
this.WhenAnyValue(x => x.RedScale, x => x.GreenScale, x => x.BlueScale).Subscribe(_ => ApplyScaling());
this.WhenActivated(d =>
{
_coreService = coreService;
_rgbService = rgbService;
_windowService = windowService;
_notificationService = notificationService;
_categories = new List<DeviceCategory>(device.Categories);
Device.PropertyChanged += DeviceOnPropertyChanged;
_coreService.FrameRendering += OnFrameRendering;
Device = device;
DisplayName = "Properties";
X = (int) Device.X;
Y = (int) Device.Y;
Scale = Device.Scale;
Rotation = (int) Device.Rotation;
RedScale = Device.RedScale * 100f;
GreenScale = Device.GreenScale * 100f;
BlueScale = Device.BlueScale * 100f;
CurrentColor = SKColors.White;
// We need to store the initial values to be able to restore them when the user clicks "Cancel"
_initialRedScale = Device.RedScale;
_initialGreenScale = Device.GreenScale;
_initialBlueScale = Device.BlueScale;
this.WhenAnyValue(x => x.RedScale, x => x.GreenScale, x => x.BlueScale).Subscribe(_ => ApplyScaling());
this.WhenActivated(d =>
Disposable.Create(() =>
{
Device.PropertyChanged += DeviceOnPropertyChanged;
_coreService.FrameRendering += OnFrameRendering;
_coreService.FrameRendering -= OnFrameRendering;
Device.PropertyChanged -= DeviceOnPropertyChanged;
}).DisposeWith(d);
});
}
Disposable.Create(() =>
{
_coreService.FrameRendering -= OnFrameRendering;
Device.PropertyChanged -= DeviceOnPropertyChanged;
}).DisposeWith(d);
});
}
public ArtemisDevice Device { get; }
public ArtemisDevice Device { get; }
public int X
{
get => _x;
set => RaiseAndSetIfChanged(ref _x, value);
}
public int X
public int Y
{
get => _y;
set => RaiseAndSetIfChanged(ref _y, value);
}
public float Scale
{
get => _scale;
set => RaiseAndSetIfChanged(ref _scale, value);
}
public int Rotation
{
get => _rotation;
set => RaiseAndSetIfChanged(ref _rotation, value);
}
public float RedScale
{
get => _redScale;
set => RaiseAndSetIfChanged(ref _redScale, value);
}
public float GreenScale
{
get => _greenScale;
set => RaiseAndSetIfChanged(ref _greenScale, value);
}
public float BlueScale
{
get => _blueScale;
set => RaiseAndSetIfChanged(ref _blueScale, value);
}
public SKColor CurrentColor
{
get => _currentColor;
set => RaiseAndSetIfChanged(ref _currentColor, value);
}
public bool DisplayOnDevices
{
get => _displayOnDevices;
set => RaiseAndSetIfChanged(ref _displayOnDevices, value);
}
// This solution won't scale well but I don't expect there to be many more categories.
// If for some reason there will be, dynamically creating a view model per category may be more appropriate
public bool HasDeskCategory
{
get => GetCategory(DeviceCategory.Desk);
set => SetCategory(DeviceCategory.Desk, value);
}
public bool HasMonitorCategory
{
get => GetCategory(DeviceCategory.Monitor);
set => SetCategory(DeviceCategory.Monitor, value);
}
public bool HasCaseCategory
{
get => GetCategory(DeviceCategory.Case);
set => SetCategory(DeviceCategory.Case, value);
}
public bool HasRoomCategory
{
get => GetCategory(DeviceCategory.Room);
set => SetCategory(DeviceCategory.Room, value);
}
public bool HasPeripheralsCategory
{
get => GetCategory(DeviceCategory.Peripherals);
set => SetCategory(DeviceCategory.Peripherals, value);
}
public bool RequiresManualSetup => !Device.DeviceProvider.CanDetectPhysicalLayout || !Device.DeviceProvider.CanDetectLogicalLayout;
public void ApplyScaling()
{
Device.RedScale = RedScale / 100f;
Device.GreenScale = GreenScale / 100f;
Device.BlueScale = BlueScale / 100f;
_rgbService.FlushLeds = true;
}
public void ClearCustomLayout()
{
Device.CustomLayoutPath = null;
_notificationService.CreateNotification()
.WithMessage("Cleared imported layout.")
.WithSeverity(NotificationSeverity.Informational);
}
public async Task BrowseCustomLayout()
{
string[]? files = await _windowService.CreateOpenFileDialog()
.WithTitle("Select device layout file")
.HavingFilter(f => f.WithName("Layout files").WithExtension("xml"))
.ShowAsync();
if (files?.Length > 0)
{
get => _x;
set => RaiseAndSetIfChanged(ref _x, value);
}
public int Y
{
get => _y;
set => RaiseAndSetIfChanged(ref _y, value);
}
public float Scale
{
get => _scale;
set => RaiseAndSetIfChanged(ref _scale, value);
}
public int Rotation
{
get => _rotation;
set => RaiseAndSetIfChanged(ref _rotation, value);
}
public float RedScale
{
get => _redScale;
set => RaiseAndSetIfChanged(ref _redScale, value);
}
public float GreenScale
{
get => _greenScale;
set => RaiseAndSetIfChanged(ref _greenScale, value);
}
public float BlueScale
{
get => _blueScale;
set => RaiseAndSetIfChanged(ref _blueScale, value);
}
public SKColor CurrentColor
{
get => _currentColor;
set => RaiseAndSetIfChanged(ref _currentColor, value);
}
public bool DisplayOnDevices
{
get => _displayOnDevices;
set => RaiseAndSetIfChanged(ref _displayOnDevices, value);
}
// This solution won't scale well but I don't expect there to be many more categories.
// If for some reason there will be, dynamically creating a view model per category may be more appropriate
public bool HasDeskCategory
{
get => GetCategory(DeviceCategory.Desk);
set => SetCategory(DeviceCategory.Desk, value);
}
public bool HasMonitorCategory
{
get => GetCategory(DeviceCategory.Monitor);
set => SetCategory(DeviceCategory.Monitor, value);
}
public bool HasCaseCategory
{
get => GetCategory(DeviceCategory.Case);
set => SetCategory(DeviceCategory.Case, value);
}
public bool HasRoomCategory
{
get => GetCategory(DeviceCategory.Room);
set => SetCategory(DeviceCategory.Room, value);
}
public bool HasPeripheralsCategory
{
get => GetCategory(DeviceCategory.Peripherals);
set => SetCategory(DeviceCategory.Peripherals, value);
}
public bool RequiresManualSetup => !Device.DeviceProvider.CanDetectPhysicalLayout || !Device.DeviceProvider.CanDetectLogicalLayout;
public void ApplyScaling()
{
Device.RedScale = RedScale / 100f;
Device.GreenScale = GreenScale / 100f;
Device.BlueScale = BlueScale / 100f;
_rgbService.FlushLeds = true;
}
public void ClearCustomLayout()
{
Device.CustomLayoutPath = null;
Device.CustomLayoutPath = files[0];
_notificationService.CreateNotification()
.WithMessage("Cleared imported layout.")
.WithTitle("Imported layout")
.WithMessage($"File loaded from {files[0]}")
.WithSeverity(NotificationSeverity.Informational);
}
}
public async Task BrowseCustomLayout()
{
string[]? files = await _windowService.CreateOpenFileDialog()
.WithTitle("Select device layout file")
.HavingFilter(f => f.WithName("Layout files").WithExtension("xml"))
.ShowAsync();
public async Task RestartSetup()
{
if (!RequiresManualSetup)
return;
if (!Device.DeviceProvider.CanDetectPhysicalLayout && !await DevicePhysicalLayoutDialogViewModel.SelectPhysicalLayout(_windowService, Device))
return;
if (!Device.DeviceProvider.CanDetectLogicalLayout && !await DeviceLogicalLayoutDialogViewModel.SelectLogicalLayout(_windowService, Device))
return;
if (files?.Length > 0)
{
Device.CustomLayoutPath = files[0];
_notificationService.CreateNotification()
.WithTitle("Imported layout")
.WithMessage($"File loaded from {files[0]}")
.WithSeverity(NotificationSeverity.Informational);
}
}
await Task.Delay(400);
_rgbService.SaveDevice(Device);
_rgbService.ApplyBestDeviceLayout(Device);
}
public async Task RestartSetup()
{
if (!RequiresManualSetup)
return;
if (!Device.DeviceProvider.CanDetectPhysicalLayout && !await DevicePhysicalLayoutDialogViewModel.SelectPhysicalLayout(_windowService, Device))
return;
if (!Device.DeviceProvider.CanDetectLogicalLayout && !await DeviceLogicalLayoutDialogViewModel.SelectLogicalLayout(_windowService, Device))
return;
public async Task Apply()
{
// TODO: Validation
await Task.Delay(400);
_rgbService.SaveDevice(Device);
_rgbService.ApplyBestDeviceLayout(Device);
}
_coreService.ProfileRenderingDisabled = true;
await Task.Delay(100);
public async Task Apply()
{
// TODO: Validation
Device.X = X;
Device.Y = Y;
Device.Scale = Scale;
Device.Rotation = Rotation;
Device.RedScale = RedScale / 100f;
Device.GreenScale = GreenScale / 100f;
Device.BlueScale = BlueScale / 100f;
Device.Categories.Clear();
foreach (DeviceCategory deviceCategory in _categories)
Device.Categories.Add(deviceCategory);
_coreService.ProfileRenderingDisabled = true;
await Task.Delay(100);
_rgbService.SaveDevice(Device);
Device.X = X;
Device.Y = Y;
Device.Scale = Scale;
Device.Rotation = Rotation;
Device.RedScale = RedScale / 100f;
Device.GreenScale = GreenScale / 100f;
Device.BlueScale = BlueScale / 100f;
Device.Categories.Clear();
foreach (DeviceCategory deviceCategory in _categories)
Device.Categories.Add(deviceCategory);
_coreService.ProfileRenderingDisabled = false;
}
_rgbService.SaveDevice(Device);
public void Reset()
{
HasDeskCategory = Device.Categories.Contains(DeviceCategory.Desk);
HasMonitorCategory = Device.Categories.Contains(DeviceCategory.Monitor);
HasCaseCategory = Device.Categories.Contains(DeviceCategory.Case);
HasRoomCategory = Device.Categories.Contains(DeviceCategory.Room);
HasPeripheralsCategory = Device.Categories.Contains(DeviceCategory.Peripherals);
_coreService.ProfileRenderingDisabled = false;
}
RedScale = _initialRedScale * 100;
GreenScale = _initialGreenScale * 100;
BlueScale = _initialBlueScale * 100;
}
public void Reset()
{
HasDeskCategory = Device.Categories.Contains(DeviceCategory.Desk);
HasMonitorCategory = Device.Categories.Contains(DeviceCategory.Monitor);
HasCaseCategory = Device.Categories.Contains(DeviceCategory.Case);
HasRoomCategory = Device.Categories.Contains(DeviceCategory.Room);
HasPeripheralsCategory = Device.Categories.Contains(DeviceCategory.Peripherals);
private bool GetCategory(DeviceCategory category)
{
return _categories.Contains(category);
}
RedScale = _initialRedScale * 100;
GreenScale = _initialGreenScale * 100;
BlueScale = _initialBlueScale * 100;
}
private void SetCategory(DeviceCategory category, bool value)
{
if (value && !_categories.Contains(category))
_categories.Add(category);
else if (!value)
_categories.Remove(category);
private bool GetCategory(DeviceCategory category)
{
return _categories.Contains(category);
}
this.RaisePropertyChanged($"Has{category}Category");
}
private void SetCategory(DeviceCategory category, bool value)
{
if (value && !_categories.Contains(category))
_categories.Add(category);
else if (!value)
_categories.Remove(category);
private void OnFrameRendering(object? sender, FrameRenderingEventArgs e)
{
if (!_displayOnDevices)
return;
this.RaisePropertyChanged($"Has{category}Category");
}
using SKPaint overlayPaint = new() {Color = CurrentColor};
e.Canvas.DrawRect(0, 0, e.Canvas.LocalClipBounds.Width, e.Canvas.LocalClipBounds.Height, overlayPaint);
}
private void OnFrameRendering(object? sender, FrameRenderingEventArgs e)
{
if (!_displayOnDevices)
return;
using SKPaint overlayPaint = new() {Color = CurrentColor};
e.Canvas.DrawRect(0, 0, e.Canvas.LocalClipBounds.Width, e.Canvas.LocalClipBounds.Height, overlayPaint);
}
private void DeviceOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Device.CustomLayoutPath) || e.PropertyName == nameof(Device.DisableDefaultLayout))
Task.Run(() => _rgbService.ApplyBestDeviceLayout(Device));
}
private void DeviceOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Device.CustomLayoutPath) || e.PropertyName == nameof(Device.DisableDefaultLayout))
Task.Run(() => _rgbService.ApplyBestDeviceLayout(Device));
}
}
@@ -1,19 +1,17 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Device
{
public partial class InputMappingsTabView : ReactiveUserControl<InputMappingsTabViewModel>
{
public InputMappingsTabView()
{
InitializeComponent();
}
namespace Artemis.UI.Screens.Device;
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public class InputMappingsTabView : ReactiveUserControl<InputMappingsTabViewModel>
{
public InputMappingsTabView()
{
InitializeComponent();
}
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
@@ -1,5 +1,4 @@
using System;
using System.Collections.ObjectModel;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Reactive.Disposables;
@@ -10,92 +9,91 @@ using Artemis.UI.Shared;
using ReactiveUI;
using RGB.NET.Core;
namespace Artemis.UI.Screens.Device
namespace Artemis.UI.Screens.Device;
public class InputMappingsTabViewModel : ActivatableViewModelBase
{
public class InputMappingsTabViewModel : ActivatableViewModelBase
private readonly IInputService _inputService;
private readonly IRgbService _rgbService;
private readonly ObservableCollection<ArtemisLed> _selectedLeds;
private ArtemisLed? _selectedLed;
public InputMappingsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds, IRgbService rgbService, IInputService inputService)
{
private readonly IRgbService _rgbService;
private readonly IInputService _inputService;
private readonly ObservableCollection<ArtemisLed> _selectedLeds;
private ArtemisLed? _selectedLed;
if (device.DeviceType != RGBDeviceType.Keyboard)
throw new ArtemisUIException("The input mappings tab only supports keyboards");
public InputMappingsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds, IRgbService rgbService, IInputService inputService)
_rgbService = rgbService;
_inputService = inputService;
_selectedLeds = selectedLeds;
Device = device;
DisplayName = "Input Mappings";
InputMappings = new ObservableCollection<(ArtemisLed, ArtemisLed)>();
this.WhenActivated(d =>
{
if (device.DeviceType != RGBDeviceType.Keyboard)
throw new ArtemisUIException("The input mappings tab only supports keyboards");
_selectedLeds.CollectionChanged += SelectedLedsOnCollectionChanged;
_inputService.KeyboardKeyUp += InputServiceOnKeyboardKeyUp;
UpdateInputMappings();
_rgbService = rgbService;
_inputService = inputService;
_selectedLeds = selectedLeds;
Device = device;
DisplayName = "Input Mappings";
InputMappings = new ObservableCollection<(ArtemisLed, ArtemisLed)>();
this.WhenActivated(d =>
Disposable.Create(() =>
{
_selectedLeds.CollectionChanged += SelectedLedsOnCollectionChanged;
_inputService.KeyboardKeyUp += InputServiceOnKeyboardKeyUp;
UpdateInputMappings();
Disposable.Create(() =>
{
_selectedLeds.CollectionChanged -= SelectedLedsOnCollectionChanged;
_inputService.KeyboardKeyUp -= InputServiceOnKeyboardKeyUp;
InputMappings.Clear();
}).DisposeWith(d);
});
}
public ArtemisDevice Device { get; }
public ArtemisLed? SelectedLed
{
get => _selectedLed;
set => RaiseAndSetIfChanged(ref _selectedLed, value);
}
public ObservableCollection<(ArtemisLed, ArtemisLed)> InputMappings { get; }
public void DeleteMapping((ArtemisLed, ArtemisLed) inputMapping)
{
Device.InputMappings.Remove(inputMapping.Item1);
UpdateInputMappings();
}
private void InputServiceOnKeyboardKeyUp(object? sender, ArtemisKeyboardKeyEventArgs e)
{
if (SelectedLed == null || e.Led == null)
return;
// Locate the original LED the same way the InputService did it, but supply false to Device.GetLed
bool foundLedId = InputKeyUtilities.KeyboardKeyLedIdMap.TryGetValue(e.Key, out LedId ledId);
if (!foundLedId)
return;
ArtemisLed? artemisLed = Device.GetLed(ledId, false);
if (artemisLed == null)
return;
// Apply the new LED mapping
Device.InputMappings[SelectedLed] = artemisLed;
_rgbService.SaveDevice(Device);
_selectedLeds.Clear();
UpdateInputMappings();
}
private void UpdateInputMappings()
{
if (InputMappings.Any())
_selectedLeds.CollectionChanged -= SelectedLedsOnCollectionChanged;
_inputService.KeyboardKeyUp -= InputServiceOnKeyboardKeyUp;
InputMappings.Clear();
}).DisposeWith(d);
});
}
foreach ((ArtemisLed, ArtemisLed) tuple in Device.InputMappings.Select(m => (m.Key, m.Value)))
InputMappings.Add(tuple);
}
public ArtemisDevice Device { get; }
private void SelectedLedsOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
SelectedLed = _selectedLeds.FirstOrDefault();
}
public ArtemisLed? SelectedLed
{
get => _selectedLed;
set => RaiseAndSetIfChanged(ref _selectedLed, value);
}
public ObservableCollection<(ArtemisLed, ArtemisLed)> InputMappings { get; }
public void DeleteMapping((ArtemisLed, ArtemisLed) inputMapping)
{
Device.InputMappings.Remove(inputMapping.Item1);
UpdateInputMappings();
}
private void InputServiceOnKeyboardKeyUp(object? sender, ArtemisKeyboardKeyEventArgs e)
{
if (SelectedLed == null || e.Led == null)
return;
// Locate the original LED the same way the InputService did it, but supply false to Device.GetLed
bool foundLedId = InputKeyUtilities.KeyboardKeyLedIdMap.TryGetValue(e.Key, out LedId ledId);
if (!foundLedId)
return;
ArtemisLed? artemisLed = Device.GetLed(ledId, false);
if (artemisLed == null)
return;
// Apply the new LED mapping
Device.InputMappings[SelectedLed] = artemisLed;
_rgbService.SaveDevice(Device);
_selectedLeds.Clear();
UpdateInputMappings();
}
private void UpdateInputMappings()
{
if (InputMappings.Any())
InputMappings.Clear();
foreach ((ArtemisLed, ArtemisLed) tuple in Device.InputMappings.Select(m => (m.Key, m.Value)))
InputMappings.Add(tuple);
}
private void SelectedLedsOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
SelectedLed = _selectedLeds.FirstOrDefault();
}
}
+11 -12
View File
@@ -1,18 +1,17 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Home
{
public class HomeView : ReactiveUserControl<HomeViewModel>
{
public HomeView()
{
InitializeComponent();
}
namespace Artemis.UI.Screens.Home;
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public class HomeView : ReactiveUserControl<HomeViewModel>
{
public HomeView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
+14 -15
View File
@@ -1,22 +1,21 @@
using Artemis.UI.Shared;
using ReactiveUI;
namespace Artemis.UI.Screens
namespace Artemis.UI.Screens;
public abstract class MainScreenViewModel : ActivatableViewModelBase, IRoutableViewModel
{
public abstract class MainScreenViewModel : ActivatableViewModelBase, IRoutableViewModel
protected MainScreenViewModel(IScreen hostScreen, string urlPathSegment)
{
protected MainScreenViewModel(IScreen hostScreen, string urlPathSegment)
{
HostScreen = hostScreen;
UrlPathSegment = urlPathSegment;
}
/// <inheritdoc />
public string UrlPathSegment { get; }
/// <inheritdoc />
public IScreen HostScreen { get; }
public ViewModelBase? TitleBarViewModel { get; protected set; }
HostScreen = hostScreen;
UrlPathSegment = urlPathSegment;
}
public ViewModelBase? TitleBarViewModel { get; protected set; }
/// <inheritdoc />
public string UrlPathSegment { get; }
/// <inheritdoc />
public IScreen HostScreen { get; }
}
@@ -1,11 +1,9 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Plugins;
public partial class PluginPrerequisitesInstallDialogView : ReactiveUserControl<PluginPrerequisitesInstallDialogViewModel>
public class PluginPrerequisitesInstallDialogView : ReactiveUserControl<PluginPrerequisitesInstallDialogViewModel>
{
public PluginPrerequisitesInstallDialogView()
{
@@ -15,136 +15,135 @@ using FluentAvalonia.UI.Controls;
using ReactiveUI;
using ContentDialogButton = Artemis.UI.Shared.Services.Builders.ContentDialogButton;
namespace Artemis.UI.Screens.Plugins
namespace Artemis.UI.Screens.Plugins;
public class PluginPrerequisitesInstallDialogViewModel : ContentDialogViewModelBase
{
public class PluginPrerequisitesInstallDialogViewModel : ContentDialogViewModelBase
private PluginPrerequisiteViewModel? _activePrerequisite;
private bool _canInstall;
private bool _showFailed;
private bool _showInstall = true;
private bool _showIntro = true;
private bool _showProgress;
private CancellationTokenSource? _tokenSource;
public PluginPrerequisitesInstallDialogViewModel(List<IPrerequisitesSubject> subjects, IPrerequisitesVmFactory prerequisitesVmFactory)
{
private PluginPrerequisiteViewModel? _activePrerequisite;
private bool _canInstall;
private bool _showFailed;
private bool _showInstall = true;
private bool _showIntro = true;
private bool _showProgress;
private CancellationTokenSource? _tokenSource;
Prerequisites = new ObservableCollection<PluginPrerequisiteViewModel>();
foreach (PluginPrerequisite prerequisite in subjects.SelectMany(prerequisitesSubject => prerequisitesSubject.PlatformPrerequisites))
Prerequisites.Add(prerequisitesVmFactory.PluginPrerequisiteViewModel(prerequisite, false));
Install = ReactiveCommand.CreateFromTask(ExecuteInstall, this.WhenAnyValue(vm => vm.CanInstall));
public PluginPrerequisitesInstallDialogViewModel(List<IPrerequisitesSubject> subjects, IPrerequisitesVmFactory prerequisitesVmFactory)
Dispatcher.UIThread.Post(() => CanInstall = Prerequisites.Any(p => !p.PluginPrerequisite.IsMet()), DispatcherPriority.Background);
this.WhenActivated(d =>
{
Prerequisites = new ObservableCollection<PluginPrerequisiteViewModel>();
foreach (PluginPrerequisite prerequisite in subjects.SelectMany(prerequisitesSubject => prerequisitesSubject.PlatformPrerequisites))
Prerequisites.Add(prerequisitesVmFactory.PluginPrerequisiteViewModel(prerequisite, false));
Install = ReactiveCommand.CreateFromTask(ExecuteInstall, this.WhenAnyValue(vm => vm.CanInstall));
Dispatcher.UIThread.Post(() => CanInstall = Prerequisites.Any(p => !p.PluginPrerequisite.IsMet()), DispatcherPriority.Background);
this.WhenActivated(d =>
Disposable.Create(() =>
{
Disposable.Create(() =>
{
_tokenSource?.Cancel();
_tokenSource?.Dispose();
_tokenSource = null;
}).DisposeWith(d);
});
}
_tokenSource?.Cancel();
_tokenSource?.Dispose();
_tokenSource = null;
}).DisposeWith(d);
});
}
public ReactiveCommand<Unit, Unit> Install { get; }
public ObservableCollection<PluginPrerequisiteViewModel> Prerequisites { get; }
public ReactiveCommand<Unit, Unit> Install { get; }
public ObservableCollection<PluginPrerequisiteViewModel> Prerequisites { get; }
public PluginPrerequisiteViewModel? ActivePrerequisite
public PluginPrerequisiteViewModel? ActivePrerequisite
{
get => _activePrerequisite;
set => RaiseAndSetIfChanged(ref _activePrerequisite, value);
}
public bool ShowProgress
{
get => _showProgress;
set => RaiseAndSetIfChanged(ref _showProgress, value);
}
public bool ShowIntro
{
get => _showIntro;
set => RaiseAndSetIfChanged(ref _showIntro, value);
}
public bool ShowFailed
{
get => _showFailed;
set => RaiseAndSetIfChanged(ref _showFailed, value);
}
public bool ShowInstall
{
get => _showInstall;
set => RaiseAndSetIfChanged(ref _showInstall, value);
}
public bool CanInstall
{
get => _canInstall;
set => RaiseAndSetIfChanged(ref _canInstall, value);
}
public static async Task Show(IWindowService windowService, List<IPrerequisitesSubject> subjects)
{
await windowService.CreateContentDialog()
.WithTitle("Plugin prerequisites")
.WithViewModel(out PluginPrerequisitesInstallDialogViewModel vm, ("subjects", subjects))
.WithCloseButtonText("Cancel")
.HavingPrimaryButton(b => b.WithText("Install").WithCommand(vm.Install))
.WithDefaultButton(ContentDialogButton.Primary)
.ShowAsync();
}
private async Task ExecuteInstall()
{
ContentDialogClosingDeferral? deferral = null;
if (ContentDialog != null)
ContentDialog.Closing += (_, args) => deferral = args.GetDeferral();
CanInstall = false;
ShowFailed = false;
ShowIntro = false;
ShowProgress = true;
_tokenSource?.Dispose();
_tokenSource = new CancellationTokenSource();
try
{
get => _activePrerequisite;
set => RaiseAndSetIfChanged(ref _activePrerequisite, value);
}
public bool ShowProgress
{
get => _showProgress;
set => RaiseAndSetIfChanged(ref _showProgress, value);
}
public bool ShowIntro
{
get => _showIntro;
set => RaiseAndSetIfChanged(ref _showIntro, value);
}
public bool ShowFailed
{
get => _showFailed;
set => RaiseAndSetIfChanged(ref _showFailed, value);
}
public bool ShowInstall
{
get => _showInstall;
set => RaiseAndSetIfChanged(ref _showInstall, value);
}
public bool CanInstall
{
get => _canInstall;
set => RaiseAndSetIfChanged(ref _canInstall, value);
}
private async Task ExecuteInstall()
{
ContentDialogClosingDeferral? deferral = null;
if (ContentDialog != null)
ContentDialog.Closing += (_, args) => deferral = args.GetDeferral();
CanInstall = false;
ShowFailed = false;
ShowIntro = false;
ShowProgress = true;
_tokenSource?.Dispose();
_tokenSource = new CancellationTokenSource();
try
foreach (PluginPrerequisiteViewModel pluginPrerequisiteViewModel in Prerequisites)
{
foreach (PluginPrerequisiteViewModel pluginPrerequisiteViewModel in Prerequisites)
pluginPrerequisiteViewModel.IsMet = pluginPrerequisiteViewModel.PluginPrerequisite.IsMet();
if (pluginPrerequisiteViewModel.IsMet)
continue;
ActivePrerequisite = pluginPrerequisiteViewModel;
await ActivePrerequisite.Install(_tokenSource.Token);
if (!ActivePrerequisite.IsMet)
{
pluginPrerequisiteViewModel.IsMet = pluginPrerequisiteViewModel.PluginPrerequisite.IsMet();
if (pluginPrerequisiteViewModel.IsMet)
continue;
ActivePrerequisite = pluginPrerequisiteViewModel;
await ActivePrerequisite.Install(_tokenSource.Token);
if (!ActivePrerequisite.IsMet)
{
CanInstall = true;
ShowFailed = true;
ShowProgress = false;
return;
}
// Wait after the task finished for the user to process what happened
if (pluginPrerequisiteViewModel != Prerequisites.Last())
await Task.Delay(250);
else
await Task.Delay(1000);
CanInstall = true;
ShowFailed = true;
ShowProgress = false;
return;
}
if (deferral != null)
deferral.Complete();
// Wait after the task finished for the user to process what happened
if (pluginPrerequisiteViewModel != Prerequisites.Last())
await Task.Delay(250);
else
ContentDialog?.Hide(ContentDialogResult.Primary);
await Task.Delay(1000);
}
catch (OperationCanceledException)
{
// ignored
}
}
public static async Task Show(IWindowService windowService, List<IPrerequisitesSubject> subjects)
if (deferral != null)
deferral.Complete();
else
ContentDialog?.Hide(ContentDialogResult.Primary);
}
catch (OperationCanceledException)
{
await windowService.CreateContentDialog()
.WithTitle("Plugin prerequisites")
.WithViewModel(out PluginPrerequisitesInstallDialogViewModel vm, ("subjects", subjects))
.WithCloseButtonText("Cancel")
.HavingPrimaryButton(b => b.WithText("Install").WithCommand(vm.Install))
.WithDefaultButton(ContentDialogButton.Primary)
.ShowAsync();
// ignored
}
}
}
@@ -1,11 +1,9 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Plugins;
public partial class PluginPrerequisitesUninstallDialogView : ReactiveUserControl<PluginPrerequisitesUninstallDialogViewModel>
public class PluginPrerequisitesUninstallDialogView : ReactiveUserControl<PluginPrerequisitesUninstallDialogViewModel>
{
public PluginPrerequisitesUninstallDialogView()
{
@@ -16,144 +16,143 @@ using FluentAvalonia.UI.Controls;
using ReactiveUI;
using ContentDialogButton = Artemis.UI.Shared.Services.Builders.ContentDialogButton;
namespace Artemis.UI.Screens.Plugins
namespace Artemis.UI.Screens.Plugins;
public class PluginPrerequisitesUninstallDialogViewModel : ContentDialogViewModelBase
{
public class PluginPrerequisitesUninstallDialogViewModel : ContentDialogViewModelBase
private readonly IPluginManagementService _pluginManagementService;
private readonly List<IPrerequisitesSubject> _subjects;
private readonly IWindowService _windowService;
private PluginPrerequisiteViewModel? _activePrerequisite;
private bool _canUninstall;
private CancellationTokenSource? _tokenSource;
public PluginPrerequisitesUninstallDialogViewModel(List<IPrerequisitesSubject> subjects,
IPrerequisitesVmFactory prerequisitesVmFactory,
IWindowService windowService,
IPluginManagementService pluginManagementService)
{
private readonly IPluginManagementService _pluginManagementService;
private readonly List<IPrerequisitesSubject> _subjects;
private readonly IWindowService _windowService;
private PluginPrerequisiteViewModel? _activePrerequisite;
private bool _canUninstall;
private CancellationTokenSource? _tokenSource;
_subjects = subjects;
_windowService = windowService;
_pluginManagementService = pluginManagementService;
public PluginPrerequisitesUninstallDialogViewModel(List<IPrerequisitesSubject> subjects,
IPrerequisitesVmFactory prerequisitesVmFactory,
IWindowService windowService,
IPluginManagementService pluginManagementService)
Prerequisites = new ObservableCollection<PluginPrerequisiteViewModel>();
foreach (PluginPrerequisite prerequisite in subjects.SelectMany(prerequisitesSubject => prerequisitesSubject.PlatformPrerequisites))
Prerequisites.Add(prerequisitesVmFactory.PluginPrerequisiteViewModel(prerequisite, true));
Uninstall = ReactiveCommand.CreateFromTask(ExecuteUninstall, this.WhenAnyValue(vm => vm.CanUninstall));
// Could be slow so take it off of the UI thread
Dispatcher.UIThread.Post(() => CanUninstall = Prerequisites.Any(p => p.PluginPrerequisite.IsMet()), DispatcherPriority.Background);
this.WhenActivated(d =>
{
_subjects = subjects;
_windowService = windowService;
_pluginManagementService = pluginManagementService;
Prerequisites = new ObservableCollection<PluginPrerequisiteViewModel>();
foreach (PluginPrerequisite prerequisite in subjects.SelectMany(prerequisitesSubject => prerequisitesSubject.PlatformPrerequisites))
Prerequisites.Add(prerequisitesVmFactory.PluginPrerequisiteViewModel(prerequisite, true));
Uninstall = ReactiveCommand.CreateFromTask(ExecuteUninstall, this.WhenAnyValue(vm => vm.CanUninstall));
// Could be slow so take it off of the UI thread
Dispatcher.UIThread.Post(() => CanUninstall = Prerequisites.Any(p => p.PluginPrerequisite.IsMet()), DispatcherPriority.Background);
this.WhenActivated(d =>
Disposable.Create(() =>
{
Disposable.Create(() =>
{
_tokenSource?.Cancel();
_tokenSource?.Dispose();
_tokenSource = null;
}).DisposeWith(d);
});
_tokenSource?.Cancel();
_tokenSource?.Dispose();
_tokenSource = null;
}).DisposeWith(d);
});
}
public ReactiveCommand<Unit, Unit> Uninstall { get; }
public ObservableCollection<PluginPrerequisiteViewModel> Prerequisites { get; }
public PluginPrerequisiteViewModel? ActivePrerequisite
{
get => _activePrerequisite;
set => RaiseAndSetIfChanged(ref _activePrerequisite, value);
}
public bool CanUninstall
{
get => _canUninstall;
set => RaiseAndSetIfChanged(ref _canUninstall, value);
}
public static async Task Show(IWindowService windowService, List<IPrerequisitesSubject> subjects, string cancelLabel = "Cancel")
{
await windowService.CreateContentDialog()
.WithTitle("Plugin prerequisites")
.WithViewModel(out PluginPrerequisitesUninstallDialogViewModel vm, ("subjects", subjects))
.WithCloseButtonText(cancelLabel)
.HavingPrimaryButton(b => b.WithText("Uninstall").WithCommand(vm.Uninstall))
.WithDefaultButton(ContentDialogButton.Primary)
.ShowAsync();
}
private async Task ExecuteUninstall()
{
ContentDialogClosingDeferral? deferral = null;
if (ContentDialog != null)
ContentDialog.Closing += (_, args) => deferral = args.GetDeferral();
CanUninstall = false;
// Disable all subjects that are plugins, this will disable their features too
foreach (IPrerequisitesSubject prerequisitesSubject in _subjects)
{
if (prerequisitesSubject is PluginInfo pluginInfo)
_pluginManagementService.DisablePlugin(pluginInfo.Plugin, true);
}
public ReactiveCommand<Unit, Unit> Uninstall { get; }
public ObservableCollection<PluginPrerequisiteViewModel> Prerequisites { get; }
public PluginPrerequisiteViewModel? ActivePrerequisite
// Disable all subjects that are features if still required
foreach (IPrerequisitesSubject prerequisitesSubject in _subjects)
{
get => _activePrerequisite;
set => RaiseAndSetIfChanged(ref _activePrerequisite, value);
if (prerequisitesSubject is not PluginFeatureInfo featureInfo)
continue;
// Disable the parent plugin if the feature is AlwaysEnabled
if (featureInfo.AlwaysEnabled)
_pluginManagementService.DisablePlugin(featureInfo.Plugin, true);
else if (featureInfo.Instance != null)
_pluginManagementService.DisablePluginFeature(featureInfo.Instance, true);
}
public bool CanUninstall
_tokenSource?.Dispose();
_tokenSource = new CancellationTokenSource();
try
{
get => _canUninstall;
set => RaiseAndSetIfChanged(ref _canUninstall, value);
}
private async Task ExecuteUninstall()
{
ContentDialogClosingDeferral? deferral = null;
if (ContentDialog != null)
ContentDialog.Closing += (_, args) => deferral = args.GetDeferral();
CanUninstall = false;
// Disable all subjects that are plugins, this will disable their features too
foreach (IPrerequisitesSubject prerequisitesSubject in _subjects)
foreach (PluginPrerequisiteViewModel pluginPrerequisiteViewModel in Prerequisites)
{
if (prerequisitesSubject is PluginInfo pluginInfo)
_pluginManagementService.DisablePlugin(pluginInfo.Plugin, true);
}
// Disable all subjects that are features if still required
foreach (IPrerequisitesSubject prerequisitesSubject in _subjects)
{
if (prerequisitesSubject is not PluginFeatureInfo featureInfo)
pluginPrerequisiteViewModel.IsMet = pluginPrerequisiteViewModel.PluginPrerequisite.IsMet();
if (!pluginPrerequisiteViewModel.IsMet)
continue;
// Disable the parent plugin if the feature is AlwaysEnabled
if (featureInfo.AlwaysEnabled)
_pluginManagementService.DisablePlugin(featureInfo.Plugin, true);
else if (featureInfo.Instance != null)
_pluginManagementService.DisablePluginFeature(featureInfo.Instance, true);
}
ActivePrerequisite = pluginPrerequisiteViewModel;
await ActivePrerequisite.Uninstall(_tokenSource.Token);
_tokenSource?.Dispose();
_tokenSource = new CancellationTokenSource();
try
{
foreach (PluginPrerequisiteViewModel pluginPrerequisiteViewModel in Prerequisites)
{
pluginPrerequisiteViewModel.IsMet = pluginPrerequisiteViewModel.PluginPrerequisite.IsMet();
if (!pluginPrerequisiteViewModel.IsMet)
continue;
ActivePrerequisite = pluginPrerequisiteViewModel;
await ActivePrerequisite.Uninstall(_tokenSource.Token);
// Wait after the task finished for the user to process what happened
if (pluginPrerequisiteViewModel != Prerequisites.Last())
await Task.Delay(250);
else
await Task.Delay(1000);
}
if (deferral != null)
deferral.Complete();
// Wait after the task finished for the user to process what happened
if (pluginPrerequisiteViewModel != Prerequisites.Last())
await Task.Delay(250);
else
ContentDialog?.Hide(ContentDialogResult.Primary);
// This shouldn't be happening and the experience isn't very nice for the user (too lazy to make a nice UI for such an edge case)
// but at least give some feedback
if (Prerequisites.Any(p => p.IsMet))
{
await _windowService.CreateContentDialog()
.WithTitle("Plugin prerequisites")
.WithContent("The plugin was not able to fully remove all prerequisites. \r\nPlease try again or contact the plugin creator.")
.ShowAsync();
await Show(_windowService, _subjects);
}
await Task.Delay(1000);
}
catch (OperationCanceledException)
if (deferral != null)
deferral.Complete();
else
ContentDialog?.Hide(ContentDialogResult.Primary);
// This shouldn't be happening and the experience isn't very nice for the user (too lazy to make a nice UI for such an edge case)
// but at least give some feedback
if (Prerequisites.Any(p => p.IsMet))
{
// ignored
}
finally
{
CanUninstall = true;
await _windowService.CreateContentDialog()
.WithTitle("Plugin prerequisites")
.WithContent("The plugin was not able to fully remove all prerequisites. \r\nPlease try again or contact the plugin creator.")
.ShowAsync();
await Show(_windowService, _subjects);
}
}
public static async Task Show(IWindowService windowService, List<IPrerequisitesSubject> subjects, string cancelLabel = "Cancel")
catch (OperationCanceledException)
{
await windowService.CreateContentDialog()
.WithTitle("Plugin prerequisites")
.WithViewModel(out PluginPrerequisitesUninstallDialogViewModel vm, ("subjects", subjects))
.WithCloseButtonText(cancelLabel)
.HavingPrimaryButton(b => b.WithText("Uninstall").WithCommand(vm.Uninstall))
.WithDefaultButton(ContentDialogButton.Primary)
.ShowAsync();
// ignored
}
finally
{
CanUninstall = true;
}
}
}
@@ -1,18 +1,17 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Plugins
{
public partial class PluginFeatureView : ReactiveUserControl<PluginFeatureViewModel>
{
public PluginFeatureView()
{
InitializeComponent();
}
namespace Artemis.UI.Screens.Plugins;
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public class PluginFeatureView : ReactiveUserControl<PluginFeatureViewModel>
{
public PluginFeatureView()
{
InitializeComponent();
}
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
@@ -9,209 +9,207 @@ using Artemis.Core.Services;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders;
using Avalonia;
using Avalonia.Threading;
using ReactiveUI;
namespace Artemis.UI.Screens.Plugins
namespace Artemis.UI.Screens.Plugins;
public class PluginFeatureViewModel : ActivatableViewModelBase
{
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)
{
private readonly ICoreService _coreService;
private readonly INotificationService _notificationService;
private readonly IPluginManagementService _pluginManagementService;
private readonly IWindowService _windowService;
private bool _enabling;
_coreService = coreService;
_windowService = windowService;
_notificationService = notificationService;
_pluginManagementService = pluginManagementService;
public PluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo,
bool showShield,
ICoreService coreService,
IWindowService windowService,
INotificationService notificationService,
IPluginManagementService pluginManagementService)
FeatureInfo = pluginFeatureInfo;
ShowShield = FeatureInfo.Plugin.Info.RequiresAdmin && showShield;
ShowLogsFolder = ReactiveCommand.Create(ExecuteShowLogsFolder);
ViewLoadException = ReactiveCommand.Create(ExecuteViewLoadException);
InstallPrerequisites = ReactiveCommand.CreateFromTask(ExecuteInstallPrerequisites);
RemovePrerequisites = ReactiveCommand.CreateFromTask(ExecuteRemovePrerequisites);
this.WhenActivated(d =>
{
_coreService = coreService;
_windowService = windowService;
_notificationService = notificationService;
_pluginManagementService = pluginManagementService;
_pluginManagementService.PluginFeatureEnabling += OnFeatureEnabling;
_pluginManagementService.PluginFeatureEnabled += OnFeatureEnableStopped;
_pluginManagementService.PluginFeatureEnableFailed += OnFeatureEnableStopped;
FeatureInfo = pluginFeatureInfo;
ShowShield = FeatureInfo.Plugin.Info.RequiresAdmin && showShield;
FeatureInfo.Plugin.Enabled += PluginOnToggled;
FeatureInfo.Plugin.Disabled += PluginOnToggled;
ShowLogsFolder = ReactiveCommand.Create(ExecuteShowLogsFolder);
ViewLoadException = ReactiveCommand.Create(ExecuteViewLoadException);
InstallPrerequisites = ReactiveCommand.CreateFromTask(ExecuteInstallPrerequisites);
RemovePrerequisites = ReactiveCommand.CreateFromTask(ExecuteRemovePrerequisites);
this.WhenActivated(d =>
Disposable.Create(() =>
{
_pluginManagementService.PluginFeatureEnabling += OnFeatureEnabling;
_pluginManagementService.PluginFeatureEnabled += OnFeatureEnableStopped;
_pluginManagementService.PluginFeatureEnableFailed += OnFeatureEnableStopped;
_pluginManagementService.PluginFeatureEnabling -= OnFeatureEnabling;
_pluginManagementService.PluginFeatureEnabled -= OnFeatureEnableStopped;
_pluginManagementService.PluginFeatureEnableFailed -= OnFeatureEnableStopped;
FeatureInfo.Plugin.Enabled += PluginOnToggled;
FeatureInfo.Plugin.Disabled += PluginOnToggled;
FeatureInfo.Plugin.Enabled -= PluginOnToggled;
FeatureInfo.Plugin.Disabled -= PluginOnToggled;
}).DisposeWith(d);
});
}
Disposable.Create(() =>
{
_pluginManagementService.PluginFeatureEnabling -= OnFeatureEnabling;
_pluginManagementService.PluginFeatureEnabled -= OnFeatureEnableStopped;
_pluginManagementService.PluginFeatureEnableFailed -= OnFeatureEnableStopped;
public ReactiveCommand<Unit, Unit> ShowLogsFolder { get; }
public ReactiveCommand<Unit, Unit> ViewLoadException { get; }
public ReactiveCommand<Unit, Unit> InstallPrerequisites { get; }
public ReactiveCommand<Unit, Unit> RemovePrerequisites { get; }
FeatureInfo.Plugin.Enabled -= PluginOnToggled;
FeatureInfo.Plugin.Disabled -= PluginOnToggled;
}).DisposeWith(d);
});
public PluginFeatureInfo FeatureInfo { get; }
public Exception? LoadException => FeatureInfo.LoadException;
public bool ShowShield { get; }
public bool Enabling
{
get => _enabling;
set => RaiseAndSetIfChanged(ref _enabling, value);
}
public bool IsEnabled
{
get => FeatureInfo.AlwaysEnabled || (FeatureInfo.Instance != null && FeatureInfo.Instance.IsEnabled);
set => Dispatcher.UIThread.InvokeAsync(() => UpdateEnabled(value));
}
public bool CanToggleEnabled => FeatureInfo.Plugin.IsEnabled && !FeatureInfo.AlwaysEnabled;
public bool CanInstallPrerequisites => FeatureInfo.PlatformPrerequisites.Any();
public bool CanRemovePrerequisites => FeatureInfo.PlatformPrerequisites.Any(p => p.UninstallActions.Any());
public bool IsPopupEnabled => CanInstallPrerequisites || CanRemovePrerequisites;
private void ExecuteShowLogsFolder()
{
try
{
Utilities.OpenFolder(Constants.LogsFolder);
}
catch (Exception e)
{
_windowService.ShowExceptionDialog("Welp, we couldn\'t open the logs folder for you", e);
}
}
private void ExecuteViewLoadException()
{
if (LoadException != null)
_windowService.ShowExceptionDialog("Feature failed to enable", LoadException);
}
private async Task ExecuteInstallPrerequisites()
{
if (FeatureInfo.PlatformPrerequisites.Any())
await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, new List<IPrerequisitesSubject> {FeatureInfo});
}
private async Task ExecuteRemovePrerequisites()
{
if (FeatureInfo.PlatformPrerequisites.Any(p => p.UninstallActions.Any()))
{
await PluginPrerequisitesUninstallDialogViewModel.Show(_windowService, new List<IPrerequisitesSubject> {FeatureInfo});
this.RaisePropertyChanged(nameof(IsEnabled));
}
}
private async Task UpdateEnabled(bool enable)
{
if (IsEnabled == enable)
{
this.RaisePropertyChanged(nameof(IsEnabled));
return;
}
public ReactiveCommand<Unit, Unit> ShowLogsFolder { get; }
public ReactiveCommand<Unit, Unit> ViewLoadException { get; }
public ReactiveCommand<Unit, Unit> InstallPrerequisites { get; }
public ReactiveCommand<Unit, Unit> RemovePrerequisites { get; }
public PluginFeatureInfo FeatureInfo { get; }
public Exception? LoadException => FeatureInfo.LoadException;
public bool ShowShield { get; }
public bool Enabling
if (FeatureInfo.Instance == null)
{
get => _enabling;
set => RaiseAndSetIfChanged(ref _enabling, value);
this.RaisePropertyChanged(nameof(IsEnabled));
_notificationService.CreateNotification()
.WithMessage($"Feature '{FeatureInfo.Name}' is in a broken state and cannot enable.")
.HavingButton(b => b.WithText("View logs").WithCommand(ShowLogsFolder))
.WithSeverity(NotificationSeverity.Error)
.Show();
return;
}
public bool IsEnabled
if (enable)
{
get => FeatureInfo.AlwaysEnabled || FeatureInfo.Instance != null && FeatureInfo.Instance.IsEnabled;
set => Dispatcher.UIThread.InvokeAsync(() => UpdateEnabled(value));
}
Enabling = true;
public bool CanToggleEnabled => FeatureInfo.Plugin.IsEnabled && !FeatureInfo.AlwaysEnabled;
public bool CanInstallPrerequisites => FeatureInfo.PlatformPrerequisites.Any();
public bool CanRemovePrerequisites => FeatureInfo.PlatformPrerequisites.Any(p => p.UninstallActions.Any());
public bool IsPopupEnabled => CanInstallPrerequisites || CanRemovePrerequisites;
private void ExecuteShowLogsFolder()
{
try
{
Utilities.OpenFolder(Constants.LogsFolder);
if (FeatureInfo.Plugin.Info.RequiresAdmin && !_coreService.IsElevated)
{
bool confirmed = await _windowService.ShowConfirmContentDialog("Enable feature", "The plugin of this feature requires admin rights, are you sure you want to enable it?");
if (!confirmed)
{
this.RaisePropertyChanged(nameof(IsEnabled));
return;
}
}
// Check if all prerequisites are met async
if (!FeatureInfo.ArePrerequisitesMet())
{
await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, new List<IPrerequisitesSubject> {FeatureInfo});
if (!FeatureInfo.ArePrerequisitesMet())
{
this.RaisePropertyChanged(nameof(IsEnabled));
return;
}
}
await Dispatcher.UIThread.InvokeAsync(() => _pluginManagementService.EnablePluginFeature(FeatureInfo.Instance!, true));
}
catch (Exception e)
{
_windowService.ShowExceptionDialog("Welp, we couldn\'t open the logs folder for you", e);
}
}
private void ExecuteViewLoadException()
{
if (LoadException != null)
_windowService.ShowExceptionDialog("Feature failed to enable", LoadException);
}
private async Task ExecuteInstallPrerequisites()
{
if (FeatureInfo.PlatformPrerequisites.Any())
await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, new List<IPrerequisitesSubject> {FeatureInfo});
}
private async Task ExecuteRemovePrerequisites()
{
if (FeatureInfo.PlatformPrerequisites.Any(p => p.UninstallActions.Any()))
{
await PluginPrerequisitesUninstallDialogViewModel.Show(_windowService, new List<IPrerequisitesSubject> {FeatureInfo});
this.RaisePropertyChanged(nameof(IsEnabled));
}
}
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.")
.WithMessage($"Failed to enable '{FeatureInfo.Name}'.\r\n{e.Message}")
.HavingButton(b => b.WithText("View logs").WithCommand(ShowLogsFolder))
.WithSeverity(NotificationSeverity.Error)
.Show();
return;
}
if (enable)
finally
{
Enabling = true;
try
{
if (FeatureInfo.Plugin.Info.RequiresAdmin && !_coreService.IsElevated)
{
bool confirmed = await _windowService.ShowConfirmContentDialog("Enable feature", "The plugin of this feature requires admin rights, are you sure you want to enable it?");
if (!confirmed)
{
this.RaisePropertyChanged(nameof(IsEnabled));
return;
}
}
// Check if all prerequisites are met async
if (!FeatureInfo.ArePrerequisitesMet())
{
await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, new List<IPrerequisitesSubject> {FeatureInfo});
if (!FeatureInfo.ArePrerequisitesMet())
{
this.RaisePropertyChanged(nameof(IsEnabled));
return;
}
}
await Dispatcher.UIThread.InvokeAsync(() => _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").WithCommand(ShowLogsFolder))
.WithSeverity(NotificationSeverity.Error)
.Show();
}
finally
{
Enabling = false;
}
}
else
{
_pluginManagementService.DisablePluginFeature(FeatureInfo.Instance, true);
this.RaisePropertyChanged(nameof(IsEnabled));
Enabling = false;
}
}
private void OnFeatureEnabling(object? sender, PluginFeatureEventArgs e)
else
{
if (e.PluginFeature != FeatureInfo.Instance) return;
Enabling = true;
}
private void OnFeatureEnableStopped(object? sender, PluginFeatureEventArgs e)
{
if (e.PluginFeature != FeatureInfo.Instance) return;
Enabling = false;
_pluginManagementService.DisablePluginFeature(FeatureInfo.Instance, true);
this.RaisePropertyChanged(nameof(IsEnabled));
this.RaisePropertyChanged(nameof(LoadException));
}
private void PluginOnToggled(object? sender, EventArgs e)
{
this.RaisePropertyChanged(nameof(CanToggleEnabled));
}
}
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));
}
}
@@ -1,10 +1,9 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Screens.Plugins;
public partial class PluginPlatformView : UserControl
public class PluginPlatformView : UserControl
{
public PluginPlatformView()
{
@@ -1,10 +1,9 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Screens.Plugins;
public partial class PluginPrerequisiteActionView : UserControl
public class PluginPrerequisiteActionView : UserControl
{
public PluginPrerequisiteActionView()
{
@@ -1,15 +1,14 @@
using Artemis.Core;
using Artemis.UI.Shared;
namespace Artemis.UI.Screens.Plugins
{
public class PluginPrerequisiteActionViewModel : ViewModelBase
{
public PluginPrerequisiteActionViewModel(PluginPrerequisiteAction action)
{
Action = action;
}
namespace Artemis.UI.Screens.Plugins;
public PluginPrerequisiteAction Action { get; }
public class PluginPrerequisiteActionViewModel : ViewModelBase
{
public PluginPrerequisiteActionViewModel(PluginPrerequisiteAction action)
{
Action = action;
}
public PluginPrerequisiteAction Action { get; }
}
@@ -1,11 +1,9 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Plugins;
public partial class PluginPrerequisiteView : ReactiveUserControl<PluginPrerequisiteViewModel>
public class PluginPrerequisiteView : ReactiveUserControl<PluginPrerequisiteViewModel>
{
public PluginPrerequisiteView()
{
@@ -8,120 +8,119 @@ using Artemis.Core;
using Artemis.UI.Shared;
using ReactiveUI;
namespace Artemis.UI.Screens.Plugins
namespace Artemis.UI.Screens.Plugins;
public class PluginPrerequisiteViewModel : ActivatableViewModelBase
{
public class PluginPrerequisiteViewModel : ActivatableViewModelBase
private readonly ObservableAsPropertyHelper<int> _activeStepNumber;
private readonly ObservableAsPropertyHelper<bool> _busy;
private readonly bool _uninstall;
private PluginPrerequisiteActionViewModel? _activeAction;
private bool _installing;
private bool _isMet;
private bool _uninstalling;
public PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, bool uninstall)
{
private readonly ObservableAsPropertyHelper<int> _activeStepNumber;
private readonly ObservableAsPropertyHelper<bool> _busy;
private readonly bool _uninstall;
_uninstall = uninstall;
private PluginPrerequisiteActionViewModel? _activeAction;
PluginPrerequisite = pluginPrerequisite;
Actions = new ObservableCollection<PluginPrerequisiteActionViewModel>(!_uninstall
? PluginPrerequisite.InstallActions.Select(a => new PluginPrerequisiteActionViewModel(a))
: PluginPrerequisite.UninstallActions.Select(a => new PluginPrerequisiteActionViewModel(a)));
private bool _installing;
private bool _isMet;
private bool _uninstalling;
_busy = this.WhenAnyValue(x => x.Installing, x => x.Uninstalling, (i, u) => i || u).ToProperty(this, x => x.Busy);
_activeStepNumber = this.WhenAnyValue(x => x.ActiveAction, a => Actions.IndexOf(a!) + 1).ToProperty(this, x => x.ActiveStepNumber);
public PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, bool uninstall)
this.WhenActivated(d =>
{
_uninstall = uninstall;
PluginPrerequisite.PropertyChanged += PluginPrerequisiteOnPropertyChanged;
Disposable.Create(() => PluginPrerequisite.PropertyChanged -= PluginPrerequisiteOnPropertyChanged).DisposeWith(d);
});
PluginPrerequisite = pluginPrerequisite;
Actions = new ObservableCollection<PluginPrerequisiteActionViewModel>(!_uninstall
? PluginPrerequisite.InstallActions.Select(a => new PluginPrerequisiteActionViewModel(a))
: PluginPrerequisite.UninstallActions.Select(a => new PluginPrerequisiteActionViewModel(a)));
// Could be slow so take it off of the UI thread
Task.Run(() => IsMet = PluginPrerequisite.IsMet());
}
_busy = this.WhenAnyValue(x => x.Installing, x => x.Uninstalling, (i, u) => i || u).ToProperty(this, x => x.Busy);
_activeStepNumber = this.WhenAnyValue(x => x.ActiveAction, a => Actions.IndexOf(a!) + 1).ToProperty(this, x => x.ActiveStepNumber);
public ObservableCollection<PluginPrerequisiteActionViewModel> Actions { get; }
this.WhenActivated(d =>
{
PluginPrerequisite.PropertyChanged += PluginPrerequisiteOnPropertyChanged;
Disposable.Create(() => PluginPrerequisite.PropertyChanged -= PluginPrerequisiteOnPropertyChanged).DisposeWith(d);
});
public PluginPrerequisiteActionViewModel? ActiveAction
{
get => _activeAction;
set => RaiseAndSetIfChanged(ref _activeAction, value);
}
// Could be slow so take it off of the UI thread
Task.Run(() => IsMet = PluginPrerequisite.IsMet());
public PluginPrerequisite PluginPrerequisite { get; }
public bool Installing
{
get => _installing;
set => RaiseAndSetIfChanged(ref _installing, value);
}
public bool Uninstalling
{
get => _uninstalling;
set => RaiseAndSetIfChanged(ref _uninstalling, value);
}
public bool IsMet
{
get => _isMet;
set => RaiseAndSetIfChanged(ref _isMet, value);
}
public bool Busy => _busy.Value;
public int ActiveStepNumber => _activeStepNumber.Value;
public async Task Install(CancellationToken cancellationToken)
{
if (Busy)
return;
Installing = true;
try
{
await PluginPrerequisite.Install(cancellationToken);
}
public ObservableCollection<PluginPrerequisiteActionViewModel> Actions { get; }
public PluginPrerequisiteActionViewModel? ActiveAction
finally
{
get => _activeAction;
set => RaiseAndSetIfChanged(ref _activeAction, value);
}
public PluginPrerequisite PluginPrerequisite { get; }
public bool Installing
{
get => _installing;
set => RaiseAndSetIfChanged(ref _installing, value);
}
public bool Uninstalling
{
get => _uninstalling;
set => RaiseAndSetIfChanged(ref _uninstalling, value);
}
public bool IsMet
{
get => _isMet;
set => RaiseAndSetIfChanged(ref _isMet, value);
}
public bool Busy => _busy.Value;
public int ActiveStepNumber => _activeStepNumber.Value;
public async Task Install(CancellationToken cancellationToken)
{
if (Busy)
return;
Installing = true;
try
{
await PluginPrerequisite.Install(cancellationToken);
}
finally
{
Installing = false;
IsMet = PluginPrerequisite.IsMet();
}
}
public async Task Uninstall(CancellationToken cancellationToken)
{
if (Busy)
return;
Uninstalling = true;
try
{
await PluginPrerequisite.Uninstall(cancellationToken);
}
finally
{
Uninstalling = false;
IsMet = PluginPrerequisite.IsMet();
}
}
private void PluginPrerequisiteOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(PluginPrerequisite.CurrentAction))
ActivateCurrentAction();
}
private void ActivateCurrentAction()
{
PluginPrerequisiteActionViewModel? activeAction = Actions.FirstOrDefault(i => i.Action == PluginPrerequisite.CurrentAction);
if (activeAction == null)
return;
ActiveAction = activeAction;
Installing = false;
IsMet = PluginPrerequisite.IsMet();
}
}
public async Task Uninstall(CancellationToken cancellationToken)
{
if (Busy)
return;
Uninstalling = true;
try
{
await PluginPrerequisite.Uninstall(cancellationToken);
}
finally
{
Uninstalling = false;
IsMet = PluginPrerequisite.IsMet();
}
}
private void PluginPrerequisiteOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(PluginPrerequisite.CurrentAction))
ActivateCurrentAction();
}
private void ActivateCurrentAction()
{
PluginPrerequisiteActionViewModel? activeAction = Actions.FirstOrDefault(i => i.Action == PluginPrerequisite.CurrentAction);
if (activeAction == null)
return;
ActiveAction = activeAction;
}
}
@@ -1,18 +1,17 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Plugins
{
public partial class PluginSettingsView : ReactiveUserControl<PluginSettingsViewModel>
{
public PluginSettingsView()
{
InitializeComponent();
}
namespace Artemis.UI.Screens.Plugins;
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public class PluginSettingsView : ReactiveUserControl<PluginSettingsViewModel>
{
public PluginSettingsView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
@@ -13,10 +13,10 @@ namespace Artemis.UI.Screens.Plugins;
public class PluginSettingsViewModel : ActivatableViewModelBase
{
private readonly INotificationService _notificationService;
private readonly Plugin _plugin;
private readonly IPluginManagementService _pluginManagementService;
private readonly INotificationService _notificationService;
public PluginSettingsViewModel(Plugin plugin, ISettingsVmFactory settingsVmFactory, IPluginManagementService pluginManagementService, INotificationService notificationService)
{
@@ -48,6 +48,8 @@ public class PluginSettingsViewModel : ActivatableViewModelBase
_notificationService.CreateNotification().WithTitle("Reloaded plugin.").Show();
}
else if (plugin == null)
{
_notificationService.CreateNotification().WithTitle("Failed to load plugin after unloading it.").Show();
}
}
}
@@ -4,35 +4,33 @@ using System.Reactive.Linq;
using Artemis.UI.Shared;
using Avalonia;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using ReactiveUI;
namespace Artemis.UI.Screens.Plugins
namespace Artemis.UI.Screens.Plugins;
public class PluginSettingsWindowView : ReactiveCoreWindow<PluginSettingsWindowViewModel>
{
public class PluginSettingsWindowView : ReactiveCoreWindow<PluginSettingsWindowViewModel>
public PluginSettingsWindowView()
{
public PluginSettingsWindowView()
{
InitializeComponent();
InitializeComponent();
#if DEBUG
this.AttachDevTools();
this.AttachDevTools();
#endif
this.WhenActivated(disposables =>
{
Observable.FromEventPattern(
x => ViewModel!.ConfigurationViewModel.CloseRequested += x,
x => ViewModel!.ConfigurationViewModel.CloseRequested -= x
)
.Subscribe(_ => Close())
.DisposeWith(disposables);
}
);
}
this.WhenActivated(disposables =>
{
Observable.FromEventPattern(
x => ViewModel!.ConfigurationViewModel.CloseRequested += x,
x => ViewModel!.ConfigurationViewModel.CloseRequested -= x
)
.Subscribe(_ => Close())
.DisposeWith(disposables);
}
);
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
@@ -2,19 +2,18 @@
using Artemis.Core;
using Artemis.UI.Shared;
namespace Artemis.UI.Screens.Plugins
namespace Artemis.UI.Screens.Plugins;
public class PluginSettingsWindowViewModel : ActivatableViewModelBase
{
public class PluginSettingsWindowViewModel : ActivatableViewModelBase
public PluginSettingsWindowViewModel(PluginConfigurationViewModel configurationViewModel)
{
public PluginSettingsWindowViewModel(PluginConfigurationViewModel configurationViewModel)
{
ConfigurationViewModel = configurationViewModel ?? throw new ArgumentNullException(nameof(configurationViewModel));
Plugin = configurationViewModel.Plugin;
ConfigurationViewModel = configurationViewModel ?? throw new ArgumentNullException(nameof(configurationViewModel));
Plugin = configurationViewModel.Plugin;
DisplayName = $"{Plugin.Info.Name} | Settings";
}
public PluginConfigurationViewModel ConfigurationViewModel { get; }
public Plugin Plugin { get; }
DisplayName = $"{Plugin.Info.Name} | Settings";
}
public PluginConfigurationViewModel ConfigurationViewModel { get; }
public Plugin Plugin { get; }
}
@@ -1,5 +1,4 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
@@ -8,9 +7,9 @@ using Avalonia.Threading;
namespace Artemis.UI.Screens.Plugins;
public partial class PluginView : ReactiveUserControl<PluginViewModel>
public class PluginView : ReactiveUserControl<PluginViewModel>
{
private CheckBox _enabledToggle;
private readonly CheckBox _enabledToggle;
public PluginView()
{
@@ -23,7 +22,7 @@ public partial class PluginView : ReactiveUserControl<PluginViewModel>
{
AvaloniaXamlLoader.Load(this);
}
private void EnabledToggleOnClick(object? sender, RoutedEventArgs e)
{
Dispatcher.UIThread.Post(() => ViewModel?.UpdateEnabled(!ViewModel.Plugin.IsEnabled));
+114 -116
View File
@@ -116,122 +116,6 @@ public class PluginViewModel : ActivatableViewModelBase
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)
@@ -311,6 +195,120 @@ public class PluginViewModel : ActivatableViewModelBase
});
}
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);
}
}
private void OnPluginToggled(object? sender, EventArgs e)
{
Dispatcher.UIThread.Post(() =>
@@ -26,15 +26,15 @@ public class MenuBarViewModel : ActivatableViewModelBase
private readonly IProfileService _profileService;
private readonly ISettingsService _settingsService;
private readonly IWindowService _windowService;
private ObservableAsPropertyHelper<bool>? _focusFolder;
private ObservableAsPropertyHelper<bool>? _focusNone;
private ObservableAsPropertyHelper<bool>? _focusSelection;
private ProfileEditorHistory? _history;
private ObservableAsPropertyHelper<bool>? _isSuspended;
private ObservableAsPropertyHelper<bool>? _keyBindingsEnabled;
private ObservableAsPropertyHelper<ProfileConfiguration?>? _profileConfiguration;
private ObservableAsPropertyHelper<RenderProfileElement?>? _profileElement;
private ObservableAsPropertyHelper<bool>? _suspendedEditing;
private ObservableAsPropertyHelper<bool>? _focusNone;
private ObservableAsPropertyHelper<bool>? _focusFolder;
private ObservableAsPropertyHelper<bool>? _focusSelection;
private ObservableAsPropertyHelper<bool>? _keyBindingsEnabled;
public MenuBarViewModel(ILogger logger, IProfileEditorService profileEditorService, IProfileService profileService, ISettingsService settingsService, IWindowService windowService)
{
@@ -18,19 +18,19 @@ public class PlaybackViewModel : ActivatableViewModelBase
private ObservableAsPropertyHelper<TimeSpan>? _currentTime;
private ObservableAsPropertyHelper<string?>? _formattedCurrentTime;
private ObservableAsPropertyHelper<bool>? _keyBindingsEnabled;
private DateTime _lastUpdate;
private ObservableAsPropertyHelper<bool>? _playing;
private RenderProfileElement? _profileElement;
private bool _repeating;
private bool _repeatSegment;
private bool _repeatTimeline;
private DateTime _lastUpdate;
public PlaybackViewModel(IProfileEditorService profileEditorService, ISettingsService settingsService)
{
_profileEditorService = profileEditorService;
_settingsService = settingsService;
_repeatTimeline = true;
this.WhenActivated(d =>
{
_profileEditorService.ProfileElement.Subscribe(e => _profileElement = e).DisposeWith(d);
@@ -38,13 +38,13 @@ public class PlaybackViewModel : ActivatableViewModelBase
_formattedCurrentTime = _profileEditorService.Time.Select(t => $"{Math.Floor(t.TotalSeconds):00}.{t.Milliseconds:000}").ToProperty(this, vm => vm.FormattedCurrentTime).DisposeWith(d);
_playing = _profileEditorService.Playing.ToProperty(this, vm => vm.Playing).DisposeWith(d);
_keyBindingsEnabled = _profileEditorService.SuspendedKeybindings.Select(s => !s).ToProperty(this, vm => vm.KeyBindingsEnabled).DisposeWith(d);
_lastUpdate = DateTime.MinValue;
DispatcherTimer updateTimer = new(TimeSpan.FromMilliseconds(60.0 / 1000), DispatcherPriority.Render, Update);
updateTimer.Start();
Disposable.Create(() => updateTimer.Stop()).DisposeWith(d);
});
PlayFromStart = ReactiveCommand.Create(ExecutePlayFromStart, this.WhenAnyValue(vm => vm.KeyBindingsEnabled));
TogglePlay = ReactiveCommand.Create(ExecuteTogglePlay, this.WhenAnyValue(vm => vm.KeyBindingsEnabled));
GoToStart = ReactiveCommand.Create(ExecuteGoToStart);
@@ -58,7 +58,7 @@ public class PlaybackViewModel : ActivatableViewModelBase
public string? FormattedCurrentTime => _formattedCurrentTime?.Value;
public bool Playing => _playing?.Value ?? false;
public bool KeyBindingsEnabled => _keyBindingsEnabled?.Value ?? false;
public bool Repeating
{
get => _repeating;
@@ -79,11 +79,11 @@ public class PlaybackViewModel : ActivatableViewModelBase
public ReactiveCommand<Unit, Unit> PlayFromStart { get; }
public ReactiveCommand<Unit, Unit> TogglePlay { get; }
public ReactiveCommand<Unit,Unit> GoToStart { get; }
public ReactiveCommand<Unit,Unit> GoToEnd { get; }
public ReactiveCommand<Unit,Unit> GoToPreviousFrame { get; }
public ReactiveCommand<Unit,Unit> GoToNextFrame { get; }
public ReactiveCommand<Unit,Unit> CycleRepeating { get; }
public ReactiveCommand<Unit, Unit> GoToStart { get; }
public ReactiveCommand<Unit, Unit> GoToEnd { get; }
public ReactiveCommand<Unit, Unit> GoToPreviousFrame { get; }
public ReactiveCommand<Unit, Unit> GoToNextFrame { get; }
public ReactiveCommand<Unit, Unit> CycleRepeating { get; }
private void ExecutePlayFromStart()
{
@@ -154,7 +154,6 @@ public class PlaybackViewModel : ActivatableViewModelBase
RepeatSegment = false;
Repeating = false;
}
}
private TimeSpan GetCurrentSegmentStart()
@@ -1,10 +1,9 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs.AdaptionHints;
public partial class CategoryAdaptionHintView : UserControl
public class CategoryAdaptionHintView : UserControl
{
public CategoryAdaptionHintView()
{
@@ -1,10 +1,9 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs.AdaptionHints;
public partial class DeviceAdaptionHintView : UserControl
public class DeviceAdaptionHintView : UserControl
{
public DeviceAdaptionHintView()
{
@@ -4,7 +4,6 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:adaptionHints="clr-namespace:Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs.AdaptionHints"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs.AdaptionHints.KeyboardSectionAdaptionHintView"
@@ -1,10 +1,9 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs.AdaptionHints;
public partial class KeyboardSectionAdaptionHintView : UserControl
public class KeyboardSectionAdaptionHintView : UserControl
{
public KeyboardSectionAdaptionHintView()
{
@@ -1,12 +1,10 @@
using Artemis.UI.Shared;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using FluentAvalonia.UI.Controls;
namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs;
public partial class LayerHintsDialogView : ReactiveCoreWindow<LayerHintsDialogViewModel>
public class LayerHintsDialogView : ReactiveCoreWindow<LayerHintsDialogViewModel>
{
public LayerHintsDialogView()
{
@@ -34,7 +34,7 @@ public class LayerHintsDialogViewModel : DialogViewModelBase<bool>
Observable.FromEventPattern<LayerAdapterHintEventArgs>(x => layer.Adapter.AdapterHintRemoved += x, x => layer.Adapter.AdapterHintRemoved -= x)
.Subscribe(c => AdaptionHints.Remove(AdaptionHints.FirstOrDefault(h => h.AdaptionHint == c.EventArgs.AdaptionHint)!))
.DisposeWith(d);
AdaptionHints.AddRange(Layer.Adapter.AdaptionHints.Select(CreateHintViewModel));
});
}
@@ -3,7 +3,6 @@ using System.Reactive.Disposables;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using ReactiveUI;
@@ -3,7 +3,6 @@ using System.Reactive.Disposables;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using ReactiveUI;
@@ -18,13 +18,13 @@ public class ProfileTreeView : ReactiveUserControl<ProfileTreeViewModel>
private Image? _dragAdorner;
private Point _dragStartPosition;
private Point _elementDragOffset;
private TreeView _treeView;
private readonly TreeView _treeView;
public ProfileTreeView()
{
InitializeComponent();
_treeView = this.Get<TreeView>("ProfileTreeView");
AddHandler(DragDrop.DragEnterEvent, HandleDragEnterEvent, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true);
AddHandler(DragDrop.DragOverEvent, HandleDragOver, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true);
AddHandler(PointerEnterEvent, HandlePointerEnter, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true);
@@ -7,7 +7,6 @@ using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.ProfileEditor;
@@ -17,11 +16,11 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree;
public class ProfileTreeViewModel : TreeItemViewModel
{
private TreeItemViewModel? _selectedChild;
private ObservableAsPropertyHelper<bool>? _focusNone;
private ObservableAsPropertyHelper<bool>? _focusFolder;
private ObservableAsPropertyHelper<bool>? _focusNone;
private ObservableAsPropertyHelper<bool>? _focusSelection;
private ObservableAsPropertyHelper<bool>? _keyBindingsEnabled;
private TreeItemViewModel? _selectedChild;
public ProfileTreeViewModel(IWindowService windowService, IProfileEditorService profileEditorService, IProfileEditorVmFactory profileEditorVmFactory)
: base(null, null, windowService, profileEditorService, profileEditorVmFactory)
@@ -67,7 +66,7 @@ public class ProfileTreeViewModel : TreeItemViewModel
public bool FocusFolder => _focusFolder?.Value ?? false;
public bool FocusSelection => _focusSelection?.Value ?? false;
public bool KeyBindingsEnabled => _keyBindingsEnabled?.Value ?? false;
public TreeItemViewModel? SelectedChild
{
get => _selectedChild;
@@ -28,11 +28,11 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase
private RenderProfileElement? _currentProfileElement;
private bool _isExpanded;
private bool _isFlyoutOpen;
private ObservableAsPropertyHelper<bool>? _isFocused;
private ProfileElement? _profileElement;
private string? _renameValue;
private bool _renaming;
private TimeSpan _time;
private ObservableAsPropertyHelper<bool>? _isFocused;
protected TreeItemViewModel(TreeItemViewModel? parent,
ProfileElement? profileElement,
@@ -56,7 +56,7 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase
Copy = ReactiveCommand.CreateFromTask(ExecuteCopy);
Paste = ReactiveCommand.CreateFromTask(ExecutePaste, this.WhenAnyValue(vm => vm.CanPaste));
AbsorbCommand = ReactiveCommand.Create(() => true);
this.WhenActivated(d =>
{
_isFocused = ProfileEditorService.FocusMode
@@ -74,7 +74,7 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase
this.WhenAnyValue(vm => vm.IsFlyoutOpen).Subscribe(UpdateCanPaste);
}
public ReactiveCommand<Unit,bool> AbsorbCommand { get; }
public ReactiveCommand<Unit, bool> AbsorbCommand { get; }
public bool IsFocused => _isFocused?.Value ?? false;
@@ -297,10 +297,8 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase
if (focusMode == ProfileEditorFocusMode.Selection)
return currentProfileElement == ProfileElement;
if (focusMode == ProfileEditorFocusMode.Folder && currentProfileElement?.Parent != null)
{
// Any direct parent or direct siblings cause focus
return currentProfileElement.Parent == ProfileElement?.Parent || currentProfileElement.Parent.GetAllRenderElements().Any(e => e == ProfileElement);
}
return false;
}
@@ -1,5 +1,4 @@
using System;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
@@ -10,7 +9,6 @@ using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.VisualScripting;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.NodeEditor;
using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.UI.Shared.Services.ProfileEditor.Commands;
using Avalonia.Threading;
@@ -20,9 +18,9 @@ namespace Artemis.UI.Screens.ProfileEditor.Properties.DataBinding;
public class DataBindingViewModel : ActivatableViewModelBase
{
private readonly PluginSetting<bool> _alwaysApplyDataBindings;
private readonly IProfileEditorService _profileEditorService;
private readonly IWindowService _windowService;
private readonly PluginSetting<bool> _alwaysApplyDataBindings;
private ObservableAsPropertyHelper<bool>? _dataBindingEnabled;
private ObservableAsPropertyHelper<ILayerProperty?>? _layerProperty;
private ObservableAsPropertyHelper<NodeScriptViewModel?>? _nodeScriptViewModel;
@@ -1,11 +1,9 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Dialogs;
public partial class TimelineSegmentEditView : ReactiveUserControl<TimelineSegmentEditViewModel>
public class TimelineSegmentEditView : ReactiveUserControl<TimelineSegmentEditViewModel>
{
public TimelineSegmentEditView()
{
@@ -5,16 +5,10 @@ namespace Artemis.UI.Screens.ProfileEditor.Properties.Dialogs;
public class TimelineSegmentEditViewModel : ContentDialogViewModelBase
{
private double _segmentLength;
public TimelineSegmentEditViewModel(TimeSpan segmentLength)
{
SegmentLength = segmentLength.TotalSeconds;
}
public double SegmentLength
{
get => _segmentLength;
set => _segmentLength = value;
}
public double SegmentLength { get; set; }
}
@@ -37,7 +37,7 @@ public class PropertyGroupViewModel : PropertyViewModelBase, IDisposable
LayerPropertyGroup.VisibilityChanged += LayerPropertyGroupOnVisibilityChanged;
_isVisible = !LayerPropertyGroup.IsHidden;
PopulateChildren();
}
@@ -52,7 +52,7 @@ public class PropertyGroupViewModel : PropertyViewModelBase, IDisposable
LayerPropertyGroup.VisibilityChanged += LayerPropertyGroupOnVisibilityChanged;
_isVisible = !LayerPropertyGroup.IsHidden;
PopulateChildren();
}
@@ -67,7 +67,7 @@ public class PropertyGroupViewModel : PropertyViewModelBase, IDisposable
LayerPropertyGroup.VisibilityChanged += LayerPropertyGroupOnVisibilityChanged;
_isVisible = !LayerPropertyGroup.IsHidden;
PopulateChildren();
}
@@ -115,7 +115,7 @@ public class PropertyGroupViewModel : PropertyViewModelBase, IDisposable
return result;
}
public List<ITimelineKeyframeViewModel> GetAllKeyframeViewModels(bool expandedOnly)
{
List<ITimelineKeyframeViewModel> result = new();
@@ -184,7 +184,7 @@ public class PropertyGroupViewModel : PropertyViewModelBase, IDisposable
if (viewModelBase is IDisposable disposable)
disposable.Dispose();
}
_keyframeSubscription.Dispose();
}
}
@@ -4,17 +4,16 @@ using Artemis.Core;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
using Artemis.UI.Screens.ProfileEditor.Properties.Tree;
using Artemis.UI.Shared;
using DynamicData;
namespace Artemis.UI.Screens.ProfileEditor.Properties;
public class PropertyViewModel : PropertyViewModelBase, IDisposable
{
private readonly SourceList<ILayerPropertyKeyframe> _keyframes;
private bool _isExpanded;
private bool _isHighlighted;
private bool _isVisible;
private readonly SourceList<ILayerPropertyKeyframe> _keyframes;
public PropertyViewModel(ILayerProperty layerProperty, IPropertyVmFactory propertyVmFactory)
{
@@ -3,7 +3,6 @@
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:timeline="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties.Timeline"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Keyframes.TimelineKeyframeView"
ClipToBounds="False">
@@ -24,11 +24,11 @@ namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Keyframes;
public class TimelineKeyframeViewModel<T> : ActivatableViewModelBase, ITimelineKeyframeViewModel
{
private readonly IProfileEditorService _profileEditorService;
private bool _canPaste;
private bool _isFlyoutOpen;
private ObservableAsPropertyHelper<bool>? _isSelected;
private string _timestamp;
private double _x;
private bool _canPaste;
private bool _isFlyoutOpen;
public TimelineKeyframeViewModel(LayerPropertyKeyframe<T> layerPropertyKeyframe, IProfileEditorService profileEditorService)
{
@@ -58,10 +58,6 @@ public class TimelineKeyframeViewModel<T> : ActivatableViewModelBase, ITimelineK
public LayerPropertyKeyframe<T> LayerPropertyKeyframe { get; }
public ObservableCollection<TimelineEasingViewModel> EasingViewModels { get; }
public ReactiveCommand<Unit, Unit> Duplicate { get; }
public ReactiveCommand<Unit, Unit> Copy { get; }
public ReactiveCommand<Unit, Unit> Paste { get; }
public ReactiveCommand<Unit, Unit> Delete { get; }
public double X
{
@@ -93,6 +89,11 @@ public class TimelineKeyframeViewModel<T> : ActivatableViewModelBase, ITimelineK
Timestamp = $"{Math.Floor(LayerPropertyKeyframe.Position.TotalSeconds):00}.{LayerPropertyKeyframe.Position.Milliseconds:000}";
}
public ReactiveCommand<Unit, Unit> Duplicate { get; }
public ReactiveCommand<Unit, Unit> Copy { get; }
public ReactiveCommand<Unit, Unit> Paste { get; }
public ReactiveCommand<Unit, Unit> Delete { get; }
public bool IsSelected => _isSelected?.Value ?? false;
public TimeSpan Position => LayerPropertyKeyframe.Position;
public ILayerPropertyKeyframe Keyframe => LayerPropertyKeyframe;
@@ -19,16 +19,16 @@ namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Segments;
public abstract class TimelineSegmentViewModel : ActivatableViewModelBase
{
private static readonly TimeSpan NewSegmentLength = TimeSpan.FromSeconds(2);
private readonly Dictionary<ILayerPropertyKeyframe, TimeSpan> _originalKeyframePositions = new();
private readonly IProfileEditorService _profileEditorService;
private readonly IWindowService _windowService;
private TimeSpan _initialLength;
private readonly Dictionary<ILayerPropertyKeyframe, TimeSpan> _originalKeyframePositions = new();
private int _pixelsPerSecond;
private RenderProfileElement? _profileElement;
private ReactiveCommand<Unit, Unit>? _removeSegment;
private ObservableAsPropertyHelper<bool>? _showAddEnd;
private ObservableAsPropertyHelper<bool>? _showAddMain;
private ObservableAsPropertyHelper<bool>? _showAddStart;
private ReactiveCommand<Unit, Unit>? _removeSegment;
protected TimelineSegmentViewModel(IProfileEditorService profileEditorService, IWindowService windowService)
{
@@ -62,28 +62,22 @@ public abstract class TimelineSegmentViewModel : ActivatableViewModelBase
.DisposeWith(d);
if (Type == ResizeTimelineSegment.SegmentType.Start)
{
RemoveSegment = ReactiveCommand.Create(
ExecuteRemoveSegment,
this.WhenAnyValue(vm => vm.ShowAddMain).CombineLatest(this.WhenAnyValue(vm => vm.ShowAddEnd)).Select(tuple => !tuple.First || !tuple.Second)
);
}
if (Type == ResizeTimelineSegment.SegmentType.Main)
{
RemoveSegment = ReactiveCommand.Create(
ExecuteRemoveSegment,
this.WhenAnyValue(vm => vm.ShowAddEnd).CombineLatest(this.WhenAnyValue(vm => vm.ShowAddStart)).Select(tuple => !tuple.First || !tuple.Second)
);
}
if (Type == ResizeTimelineSegment.SegmentType.End)
{
RemoveSegment = ReactiveCommand.Create(
ExecuteRemoveSegment,
this.WhenAnyValue(vm => vm.ShowAddStart).CombineLatest(this.WhenAnyValue(vm => vm.ShowAddMain)).Select(tuple => !tuple.First || !tuple.Second)
);
}
});
}
@@ -167,12 +161,44 @@ public abstract class TimelineSegmentViewModel : ActivatableViewModelBase
TimeSpan time = TimeSpan.FromMilliseconds(Math.Max(GetTimeFromX(x, snap, round).TotalMilliseconds, 100));
if (_initialLength == time)
return;
using ProfileEditorCommandScope scope = _profileEditorService.CreateCommandScope("Resize segment");
ApplyPendingKeyframeMovement();
_profileEditorService.ExecuteCommand(new ResizeTimelineSegment(Type, _profileElement, time, _initialLength));
}
protected TimeSpan GetTimeFromX(double x, bool snap, bool round)
{
TimeSpan time = TimeSpan.FromSeconds(x / _pixelsPerSecond);
if (time < TimeSpan.Zero)
time = TimeSpan.Zero;
if (round)
time = _profileEditorService.RoundTime(time);
if (snap)
time = SnapToTimeline(time);
return time;
}
protected void ShiftKeyframes(IEnumerable<ILayerPropertyKeyframe> keyframes, TimeSpan amount)
{
foreach (ILayerPropertyKeyframe layerPropertyKeyframe in keyframes)
{
if (!_originalKeyframePositions.ContainsKey(layerPropertyKeyframe))
_originalKeyframePositions[layerPropertyKeyframe] = layerPropertyKeyframe.Position;
layerPropertyKeyframe.Position = layerPropertyKeyframe.Position.Add(amount);
}
}
protected void ApplyPendingKeyframeMovement()
{
foreach ((ILayerPropertyKeyframe keyframe, TimeSpan originalPosition) in _originalKeyframePositions)
_profileEditorService.ExecuteCommand(new MoveKeyframe(keyframe, keyframe.Position, originalPosition));
_originalKeyframePositions.Clear();
}
private void ExecuteRemoveSegment()
{
if (_profileElement == null)
@@ -210,38 +236,6 @@ public abstract class TimelineSegmentViewModel : ActivatableViewModelBase
.ShowAsync();
}
protected TimeSpan GetTimeFromX(double x, bool snap, bool round)
{
TimeSpan time = TimeSpan.FromSeconds(x / _pixelsPerSecond);
if (time < TimeSpan.Zero)
time = TimeSpan.Zero;
if (round)
time = _profileEditorService.RoundTime(time);
if (snap)
time = SnapToTimeline(time);
return time;
}
protected void ShiftKeyframes(IEnumerable<ILayerPropertyKeyframe> keyframes, TimeSpan amount)
{
foreach (ILayerPropertyKeyframe layerPropertyKeyframe in keyframes)
{
if (!_originalKeyframePositions.ContainsKey(layerPropertyKeyframe))
_originalKeyframePositions[layerPropertyKeyframe] = layerPropertyKeyframe.Position;
layerPropertyKeyframe.Position = layerPropertyKeyframe.Position.Add(amount);
}
}
protected void ApplyPendingKeyframeMovement()
{
foreach ((ILayerPropertyKeyframe keyframe, TimeSpan originalPosition) in _originalKeyframePositions)
_profileEditorService.ExecuteCommand(new MoveKeyframe(keyframe, keyframe.Position, originalPosition));
_originalKeyframePositions.Clear();
}
private TimeSpan SnapToTimeline(TimeSpan time)
{
TimeSpan tolerance = TimeSpan.FromMilliseconds(1000f / _pixelsPerSecond * 5);
@@ -15,8 +15,8 @@ namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
public class TimelinePropertyViewModel<T> : ActivatableViewModelBase, ITimelinePropertyViewModel
{
private readonly IProfileEditorService _profileEditorService;
private readonly SourceList<LayerPropertyKeyframe<T>> _keyframes;
private readonly IProfileEditorService _profileEditorService;
private ObservableAsPropertyHelper<bool>? _keyframesEnabled;
public TimelinePropertyViewModel(LayerProperty<T> layerProperty, PropertyViewModel propertyViewModel, IProfileEditorService profileEditorService)
@@ -1,13 +1,12 @@
using Artemis.Core.LayerBrushes;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Tree.Dialogs;
public partial class LayerBrushPresetView : ReactiveUserControl<LayerBrushPresetViewModel>
public class LayerBrushPresetView : ReactiveUserControl<LayerBrushPresetViewModel>
{
public LayerBrushPresetView()
{
@@ -1,13 +1,8 @@
using System;
using System.Collections.ObjectModel;
using System.Reactive.Linq;
using Artemis.Core;
using Artemis.Core.LayerBrushes;
using Artemis.Core.LayerEffects;
using Artemis.Core.Services;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.UI.Shared.Services.ProfileEditor.Commands;
using DynamicData;
using ReactiveUI;
@@ -48,7 +43,7 @@ public class LayerBrushPresetViewModel : ContentDialogViewModelBase
preset.Apply();
ContentDialog?.Hide();
}
private Func<ILayerBrushPreset, bool> CreatePredicate(string? search)
{
if (string.IsNullOrWhiteSpace(search))
@@ -8,7 +8,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Properties.Windows;
public class BrushConfigurationWindowView : ReactiveCoreWindow<BrushConfigurationWindowViewModel>
{
private bool _canClose;
public BrushConfigurationWindowView()
{
InitializeComponent();
@@ -27,7 +27,7 @@ public class BrushConfigurationWindowView : ReactiveCoreWindow<BrushConfiguratio
{
if (_canClose)
return;
e.Cancel = true;
if (ViewModel == null || await ViewModel.CanClose())
{
@@ -27,7 +27,7 @@ public class EffectConfigurationWindowView : ReactiveCoreWindow<EffectConfigurat
{
if (_canClose)
return;
e.Cancel = true;
if (ViewModel == null || await ViewModel.CanClose())
{
@@ -74,6 +74,23 @@ public class VisualEditorViewModel : ActivatableViewModelBase
public ReadOnlyObservableCollection<IVisualizerViewModel> Visualizers { get; }
public ObservableCollection<ArtemisDevice> Devices { get; }
public void SetTools(SourceList<IToolViewModel> tools)
{
tools.Connect()
.AutoRefreshOnObservable(t => t.WhenAnyValue(vm => vm.IsSelected))
.Filter(t => t.IsSelected)
.Bind(out ReadOnlyObservableCollection<IToolViewModel> selectedTools)
.Subscribe();
Tools = selectedTools;
}
public void RequestAutoFit()
{
AutoFitRequested?.Invoke(this, EventArgs.Empty);
}
public event EventHandler? AutoFitRequested;
private void RemoveElement(EventPattern<ProfileElementEventArgs> eventPattern)
{
List<IVisualizerViewModel> visualizers = Visualizers.Where(v => v.ProfileElement == eventPattern.EventArgs.ProfileElement).ToList();
@@ -104,21 +121,4 @@ public class VisualEditorViewModel : ActivatableViewModelBase
visualizerViewModels.Add(_vmFactory.LayerShapeVisualizerViewModel(layer));
visualizerViewModels.Add(_vmFactory.LayerVisualizerViewModel(layer));
}
public void SetTools(SourceList<IToolViewModel> tools)
{
tools.Connect()
.AutoRefreshOnObservable(t => t.WhenAnyValue(vm => vm.IsSelected))
.Filter(t => t.IsSelected)
.Bind(out ReadOnlyObservableCollection<IToolViewModel> selectedTools)
.Subscribe();
Tools = selectedTools;
}
public void RequestAutoFit()
{
AutoFitRequested?.Invoke(this, EventArgs.Empty);
}
public event EventHandler? AutoFitRequested;
}
@@ -24,13 +24,13 @@ public class ProfileEditorViewModel : MainScreenViewModel
private readonly IProfileEditorService _profileEditorService;
private readonly ISettingsService _settingsService;
private readonly SourceList<IToolViewModel> _tools;
private DisplayConditionScriptViewModel? _displayConditionScriptViewModel;
private ObservableAsPropertyHelper<ProfileEditorHistory?>? _history;
private ObservableAsPropertyHelper<ProfileConfiguration?>? _profileConfiguration;
private ObservableAsPropertyHelper<bool>? _suspendedEditing;
private StatusBarViewModel? _statusBarViewModel;
private DisplayConditionScriptViewModel? _displayConditionScriptViewModel;
private PropertiesViewModel? _propertiesViewModel;
private ProfileTreeViewModel? _profileTreeViewModel;
private PropertiesViewModel? _propertiesViewModel;
private StatusBarViewModel? _statusBarViewModel;
private ObservableAsPropertyHelper<bool>? _suspendedEditing;
private VisualEditorViewModel? _visualEditorViewModel;
/// <inheritdoc />
@@ -144,7 +144,7 @@ public class ProfileEditorViewModel : MainScreenViewModel
if (!changed.IsSelected || !changed.IsExclusive)
return;
// Disable all others if the changed one is selected and exclusive
_tools.Edit(list =>
{
@@ -1,19 +1,17 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Screens.Root
{
public partial class DefaultTitleBarView : UserControl
{
public DefaultTitleBarView()
{
InitializeComponent();
}
namespace Artemis.UI.Screens.Root;
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public class DefaultTitleBarView : UserControl
{
public DefaultTitleBarView()
{
InitializeComponent();
}
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
@@ -1,20 +1,19 @@
using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared;
namespace Artemis.UI.Screens.Root
namespace Artemis.UI.Screens.Root;
public class DefaultTitleBarViewModel : ViewModelBase
{
public class DefaultTitleBarViewModel : ViewModelBase
private readonly IDebugService _debugService;
public DefaultTitleBarViewModel(IDebugService debugService)
{
private readonly IDebugService _debugService;
_debugService = debugService;
}
public DefaultTitleBarViewModel(IDebugService debugService)
{
_debugService = debugService;
}
public void ShowDebugger()
{
_debugService.ShowDebugger();
}
public void ShowDebugger()
{
_debugService.ShowDebugger();
}
}
+11 -12
View File
@@ -1,18 +1,17 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Root
{
public class RootView : ReactiveUserControl<RootViewModel>
{
public RootView()
{
InitializeComponent();
}
namespace Artemis.UI.Screens.Root;
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public class RootView : ReactiveUserControl<RootViewModel>
{
public RootView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
+181 -183
View File
@@ -14,199 +14,197 @@ using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform;
using Avalonia.Threading;
using JetBrains.Annotations;
using ReactiveUI;
namespace Artemis.UI.Screens.Root
namespace Artemis.UI.Screens.Root;
public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvider
{
public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvider
private readonly IAssetLoader _assetLoader;
private readonly ICoreService _coreService;
private readonly IDebugService _debugService;
private readonly DefaultTitleBarViewModel _defaultTitleBarViewModel;
private readonly IClassicDesktopStyleApplicationLifetime _lifeTime;
private readonly ISettingsService _settingsService;
private readonly ISidebarVmFactory _sidebarVmFactory;
private readonly IUpdateService _updateService;
private readonly IWindowService _windowService;
private SidebarViewModel? _sidebarViewModel;
private ViewModelBase? _titleBarViewModel;
public RootViewModel(ICoreService coreService,
ISettingsService settingsService,
IRegistrationService registrationService,
IWindowService windowService,
IMainWindowService mainWindowService,
IDebugService debugService,
IUpdateService updateService,
IAssetLoader assetLoader,
DefaultTitleBarViewModel defaultTitleBarViewModel,
ISidebarVmFactory sidebarVmFactory)
{
private readonly DefaultTitleBarViewModel _defaultTitleBarViewModel;
private readonly ICoreService _coreService;
private readonly ISettingsService _settingsService;
private readonly IWindowService _windowService;
private readonly IDebugService _debugService;
private readonly IUpdateService _updateService;
private readonly IAssetLoader _assetLoader;
private readonly IClassicDesktopStyleApplicationLifetime _lifeTime;
private readonly ISidebarVmFactory _sidebarVmFactory;
private SidebarViewModel? _sidebarViewModel;
private ViewModelBase? _titleBarViewModel;
Router = new RoutingState();
public RootViewModel(ICoreService coreService,
ISettingsService settingsService,
IRegistrationService registrationService,
IWindowService windowService,
IMainWindowService mainWindowService,
IDebugService debugService,
IUpdateService updateService,
IAssetLoader assetLoader,
DefaultTitleBarViewModel defaultTitleBarViewModel,
ISidebarVmFactory sidebarVmFactory)
_coreService = coreService;
_settingsService = settingsService;
_windowService = windowService;
_debugService = debugService;
_updateService = updateService;
_assetLoader = assetLoader;
_defaultTitleBarViewModel = defaultTitleBarViewModel;
_sidebarVmFactory = sidebarVmFactory;
_lifeTime = (IClassicDesktopStyleApplicationLifetime) Application.Current!.ApplicationLifetime!;
coreService.StartupArguments = _lifeTime.Args.ToList();
mainWindowService.ConfigureMainWindowProvider(this);
DisplayAccordingToSettings();
Router.CurrentViewModel.Subscribe(UpdateTitleBarViewModel);
Task.Run(() =>
{
Router = new RoutingState();
_coreService = coreService;
_settingsService = settingsService;
_windowService = windowService;
_debugService = debugService;
_updateService = updateService;
_assetLoader = assetLoader;
_defaultTitleBarViewModel = defaultTitleBarViewModel;
_sidebarVmFactory = sidebarVmFactory;
_lifeTime = (IClassicDesktopStyleApplicationLifetime) Application.Current!.ApplicationLifetime!;
coreService.StartupArguments = _lifeTime.Args.ToList();
mainWindowService.ConfigureMainWindowProvider(this);
DisplayAccordingToSettings();
Router.CurrentViewModel.Subscribe(UpdateTitleBarViewModel);
Task.Run(() =>
{
coreService.Initialize();
registrationService.RegisterBuiltInDataModelDisplays();
registrationService.RegisterBuiltInDataModelInputs();
registrationService.RegisterBuiltInPropertyEditors();
});
}
private void UpdateTitleBarViewModel(IRoutableViewModel? viewModel)
{
if (viewModel is MainScreenViewModel mainScreenViewModel && mainScreenViewModel.TitleBarViewModel != null)
TitleBarViewModel = mainScreenViewModel.TitleBarViewModel;
else
TitleBarViewModel = _defaultTitleBarViewModel;
}
public SidebarViewModel? SidebarViewModel
{
get => _sidebarViewModel;
set => RaiseAndSetIfChanged(ref _sidebarViewModel, value);
}
public ViewModelBase? TitleBarViewModel
{
get => _titleBarViewModel;
set => RaiseAndSetIfChanged(ref _titleBarViewModel, value);
}
private void CurrentMainWindowOnClosing(object? sender, EventArgs e)
{
_lifeTime.MainWindow = null;
SidebarViewModel = null;
Router.NavigateAndReset.Execute(new EmptyViewModel(this, "blank")).Subscribe();
OnMainWindowClosed();
}
private void DisplayAccordingToSettings()
{
bool autoRunning = _coreService.StartupArguments.Contains("--autorun");
bool minimized = _coreService.StartupArguments.Contains("--minimized");
bool showOnAutoRun = _settingsService.GetSetting("UI.ShowOnStartup", true).Value;
if (autoRunning && !showOnAutoRun || minimized)
{
// TODO: Auto-update
}
else
{
ShowSplashScreen();
_coreService.Initialized += (_, _) => Dispatcher.UIThread.InvokeAsync(OpenMainWindow);
}
}
private void ShowSplashScreen()
{
_windowService.ShowWindow<SplashViewModel>();
}
/// <inheritdoc />
public RoutingState Router { get; }
#region Tray commands
public void OpenScreen(string displayName)
{
// The window will open on the UI thread at some point, respond to that to select the chosen screen
MainWindowOpened += OnEventHandler;
OpenMainWindow();
void OnEventHandler(object? sender, EventArgs args)
{
if (SidebarViewModel != null)
SidebarViewModel.SelectedSidebarScreen = SidebarViewModel.SidebarScreens.FirstOrDefault(s => s.DisplayName == displayName);
MainWindowOpened -= OnEventHandler;
}
}
public async Task OpenDebugger()
{
await Dispatcher.UIThread.InvokeAsync(() => _debugService.ShowDebugger());
}
public async Task Exit()
{
// Don't freeze the UI right after clicking
await Task.Delay(200);
Utilities.Shutdown();
}
#endregion
#region Implementation of IMainWindowProvider
/// <inheritdoc />
public bool IsMainWindowOpen => _lifeTime.MainWindow != null;
/// <inheritdoc />
public void OpenMainWindow()
{
Dispatcher.UIThread.Post(() =>
{
if (_lifeTime.MainWindow == null)
{
SidebarViewModel = _sidebarVmFactory.SidebarViewModel(this);
_lifeTime.MainWindow = new MainWindow {DataContext = this};
_lifeTime.MainWindow.Show();
_lifeTime.MainWindow.Closing += CurrentMainWindowOnClosing;
}
_lifeTime.MainWindow.WindowState = WindowState.Normal;
_lifeTime.MainWindow.Activate();
OnMainWindowOpened();
});
}
/// <inheritdoc />
public void CloseMainWindow()
{
Dispatcher.UIThread.Post(() => { _lifeTime.MainWindow?.Close(); });
}
/// <inheritdoc />
public event EventHandler? MainWindowOpened;
/// <inheritdoc />
public event EventHandler? MainWindowClosed;
protected virtual void OnMainWindowOpened()
{
MainWindowOpened?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnMainWindowClosed()
{
MainWindowClosed?.Invoke(this, EventArgs.Empty);
}
#endregion
coreService.Initialize();
registrationService.RegisterBuiltInDataModelDisplays();
registrationService.RegisterBuiltInDataModelInputs();
registrationService.RegisterBuiltInPropertyEditors();
});
}
internal class EmptyViewModel : MainScreenViewModel
public SidebarViewModel? SidebarViewModel
{
/// <inheritdoc />
public EmptyViewModel(IScreen hostScreen, string urlPathSegment) : base(hostScreen, urlPathSegment)
get => _sidebarViewModel;
set => RaiseAndSetIfChanged(ref _sidebarViewModel, value);
}
public ViewModelBase? TitleBarViewModel
{
get => _titleBarViewModel;
set => RaiseAndSetIfChanged(ref _titleBarViewModel, value);
}
private void UpdateTitleBarViewModel(IRoutableViewModel? viewModel)
{
if (viewModel is MainScreenViewModel mainScreenViewModel && mainScreenViewModel.TitleBarViewModel != null)
TitleBarViewModel = mainScreenViewModel.TitleBarViewModel;
else
TitleBarViewModel = _defaultTitleBarViewModel;
}
private void CurrentMainWindowOnClosing(object? sender, EventArgs e)
{
_lifeTime.MainWindow = null;
SidebarViewModel = null;
Router.NavigateAndReset.Execute(new EmptyViewModel(this, "blank")).Subscribe();
OnMainWindowClosed();
}
private void DisplayAccordingToSettings()
{
bool autoRunning = _coreService.StartupArguments.Contains("--autorun");
bool minimized = _coreService.StartupArguments.Contains("--minimized");
bool showOnAutoRun = _settingsService.GetSetting("UI.ShowOnStartup", true).Value;
if ((autoRunning && !showOnAutoRun) || minimized)
{
// TODO: Auto-update
}
else
{
ShowSplashScreen();
_coreService.Initialized += (_, _) => Dispatcher.UIThread.InvokeAsync(OpenMainWindow);
}
}
private void ShowSplashScreen()
{
_windowService.ShowWindow<SplashViewModel>();
}
/// <inheritdoc />
public RoutingState Router { get; }
#region Tray commands
public void OpenScreen(string displayName)
{
// The window will open on the UI thread at some point, respond to that to select the chosen screen
MainWindowOpened += OnEventHandler;
OpenMainWindow();
void OnEventHandler(object? sender, EventArgs args)
{
if (SidebarViewModel != null)
SidebarViewModel.SelectedSidebarScreen = SidebarViewModel.SidebarScreens.FirstOrDefault(s => s.DisplayName == displayName);
MainWindowOpened -= OnEventHandler;
}
}
public async Task OpenDebugger()
{
await Dispatcher.UIThread.InvokeAsync(() => _debugService.ShowDebugger());
}
public async Task Exit()
{
// Don't freeze the UI right after clicking
await Task.Delay(200);
Utilities.Shutdown();
}
#endregion
#region Implementation of IMainWindowProvider
/// <inheritdoc />
public bool IsMainWindowOpen => _lifeTime.MainWindow != null;
/// <inheritdoc />
public void OpenMainWindow()
{
Dispatcher.UIThread.Post(() =>
{
if (_lifeTime.MainWindow == null)
{
SidebarViewModel = _sidebarVmFactory.SidebarViewModel(this);
_lifeTime.MainWindow = new MainWindow {DataContext = this};
_lifeTime.MainWindow.Show();
_lifeTime.MainWindow.Closing += CurrentMainWindowOnClosing;
}
_lifeTime.MainWindow.WindowState = WindowState.Normal;
_lifeTime.MainWindow.Activate();
OnMainWindowOpened();
});
}
/// <inheritdoc />
public void CloseMainWindow()
{
Dispatcher.UIThread.Post(() => { _lifeTime.MainWindow?.Close(); });
}
/// <inheritdoc />
public event EventHandler? MainWindowOpened;
/// <inheritdoc />
public event EventHandler? MainWindowClosed;
protected virtual void OnMainWindowOpened()
{
MainWindowOpened?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnMainWindowClosed()
{
MainWindowClosed?.Invoke(this, EventArgs.Empty);
}
#endregion
}
internal class EmptyViewModel : MainScreenViewModel
{
/// <inheritdoc />
public EmptyViewModel(IScreen hostScreen, string urlPathSegment) : base(hostScreen, urlPathSegment)
{
}
}
+19 -20
View File
@@ -7,27 +7,26 @@ using Avalonia.ReactiveUI;
using Avalonia.Threading;
using ReactiveUI;
namespace Artemis.UI.Screens.Root
{
public class SplashView : ReactiveWindow<SplashViewModel>
{
public SplashView()
{
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
this.WhenActivated(disposables =>
{
Observable.FromEventPattern(x => ViewModel!.CoreService.Initialized += x, x => ViewModel!.CoreService.Initialized -= x)
.Subscribe(_ => Dispatcher.UIThread.Post(Close))
.DisposeWith(disposables);
});
}
namespace Artemis.UI.Screens.Root;
private void InitializeComponent()
public class SplashView : ReactiveWindow<SplashViewModel>
{
public SplashView()
{
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
this.WhenActivated(disposables =>
{
AvaloniaXamlLoader.Load(this);
}
Observable.FromEventPattern(x => ViewModel!.CoreService.Initialized += x, x => ViewModel!.CoreService.Initialized -= x)
.Subscribe(_ => Dispatcher.UIThread.Post(Close))
.DisposeWith(disposables);
});
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
+49 -52
View File
@@ -2,70 +2,67 @@
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Shared;
using Humanizer;
using ReactiveUI;
namespace Artemis.UI.Screens.Root
namespace Artemis.UI.Screens.Root;
public class SplashViewModel : ViewModelBase
{
public class SplashViewModel : ViewModelBase
private string _status;
public SplashViewModel(ICoreService coreService, IPluginManagementService pluginManagementService)
{
private string _status;
public SplashViewModel(ICoreService coreService, IPluginManagementService pluginManagementService)
{
CoreService = coreService;
_status = "Initializing Core";
CoreService = coreService;
_status = "Initializing Core";
pluginManagementService.CopyingBuildInPlugins += OnPluginManagementServiceOnCopyingBuildInPluginsManagement;
pluginManagementService.PluginLoading += OnPluginManagementServiceOnPluginManagementLoading;
pluginManagementService.PluginLoaded += OnPluginManagementServiceOnPluginManagementLoaded;
pluginManagementService.PluginEnabling += PluginManagementServiceOnPluginManagementEnabling;
pluginManagementService.PluginEnabled += PluginManagementServiceOnPluginManagementEnabled;
pluginManagementService.PluginFeatureEnabling += PluginManagementServiceOnPluginFeatureEnabling;
pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureEnabled;
}
pluginManagementService.CopyingBuildInPlugins += OnPluginManagementServiceOnCopyingBuildInPluginsManagement;
pluginManagementService.PluginLoading += OnPluginManagementServiceOnPluginManagementLoading;
pluginManagementService.PluginLoaded += OnPluginManagementServiceOnPluginManagementLoaded;
pluginManagementService.PluginEnabling += PluginManagementServiceOnPluginManagementEnabling;
pluginManagementService.PluginEnabled += PluginManagementServiceOnPluginManagementEnabled;
pluginManagementService.PluginFeatureEnabling += PluginManagementServiceOnPluginFeatureEnabling;
pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureEnabled;
}
public ICoreService CoreService { get; }
public ICoreService CoreService { get; }
public string Status
{
get => _status;
set => RaiseAndSetIfChanged(ref _status, value);
}
public string Status
{
get => _status;
set => RaiseAndSetIfChanged(ref _status, value);
}
private void OnPluginManagementServiceOnPluginManagementLoaded(object? sender, PluginEventArgs args)
{
Status = "Initializing UI";
}
private void OnPluginManagementServiceOnPluginManagementLoaded(object? sender, PluginEventArgs args)
{
Status = "Initializing UI";
}
private void OnPluginManagementServiceOnPluginManagementLoading(object? sender, PluginEventArgs args)
{
Status = "Loading plugin: " + args.Plugin.Info.Name;
}
private void OnPluginManagementServiceOnPluginManagementLoading(object? sender, PluginEventArgs args)
{
Status = "Loading plugin: " + args.Plugin.Info.Name;
}
private void PluginManagementServiceOnPluginManagementEnabled(object? sender, PluginEventArgs args)
{
Status = "Initializing UI";
}
private void PluginManagementServiceOnPluginManagementEnabled(object? sender, PluginEventArgs args)
{
Status = "Initializing UI";
}
private void PluginManagementServiceOnPluginManagementEnabling(object? sender, PluginEventArgs args)
{
Status = "Enabling plugin: " + args.Plugin.Info.Name;
}
private void PluginManagementServiceOnPluginManagementEnabling(object? sender, PluginEventArgs args)
{
Status = "Enabling plugin: " + args.Plugin.Info.Name;
}
private void PluginManagementServiceOnPluginFeatureEnabling(object? sender, PluginFeatureEventArgs e)
{
Status = "Enabling: " + e.PluginFeature.Info.Name;
}
private void PluginManagementServiceOnPluginFeatureEnabling(object? sender, PluginFeatureEventArgs e)
{
Status = "Enabling: " + e.PluginFeature.Info.Name;
}
private void PluginManagementServiceOnPluginFeatureEnabled(object? sender, PluginFeatureEventArgs e)
{
Status = "Initializing UI";
}
private void PluginManagementServiceOnPluginFeatureEnabled(object? sender, PluginFeatureEventArgs e)
{
Status = "Initializing UI";
}
private void OnPluginManagementServiceOnCopyingBuildInPluginsManagement(object? sender, EventArgs args)
{
Status = "Updating built-in plugins";
}
private void OnPluginManagementServiceOnCopyingBuildInPluginsManagement(object? sender, EventArgs args)
{
Status = "Updating built-in plugins";
}
}
@@ -1,11 +1,9 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Scripting.Dialogs;
public partial class ScriptConfigurationCreateView : ReactiveUserControl<ScriptConfigurationCreateViewModel>
public class ScriptConfigurationCreateView : ReactiveUserControl<ScriptConfigurationCreateViewModel>
{
public ScriptConfigurationCreateView()
{
@@ -20,7 +20,7 @@ public class ScriptConfigurationCreateViewModel : ContentDialogViewModelBase
ScriptingProviders = new List<ScriptingProvider>(scriptingService.ScriptingProviders);
Submit = ReactiveCommand.Create(ExecuteSubmit, ValidationContext.Valid);
_selectedScriptingProvider = ScriptingProviders.First();
this.ValidationRule(vm => vm.ScriptName, s => !string.IsNullOrWhiteSpace(s), "Script name cannot be empty.");
}
@@ -3,7 +3,6 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:dialogs="clr-namespace:Artemis.UI.Screens.Scripting.Dialogs"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Scripting.Dialogs.ScriptConfigurationEditView"
x:DataType="dialogs:ScriptConfigurationEditViewModel">
@@ -1,12 +1,11 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using ReactiveUI;
namespace Artemis.UI.Screens.Scripting.Dialogs;
public partial class ScriptConfigurationEditView : ReactiveUserControl<ScriptConfigurationEditViewModel>
public class ScriptConfigurationEditView : ReactiveUserControl<ScriptConfigurationEditViewModel>
{
public ScriptConfigurationEditView()
{
@@ -16,7 +16,7 @@ public class ScriptConfigurationEditViewModel : ContentDialogViewModelBase
ScriptConfiguration = scriptConfiguration;
Submit = ReactiveCommand.Create(ExecuteSubmit, ValidationContext.Valid);
ScriptName = ScriptConfiguration.Name;
this.ValidationRule(vm => vm.ScriptName, s => !string.IsNullOrWhiteSpace(s), "Script name cannot be empty.");
}
@@ -1,12 +1,11 @@
using System.ComponentModel;
using Artemis.UI.Shared;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Screens.Scripting;
public partial class ScriptsDialogView : ReactiveCoreWindow<ScriptsDialogViewModel>
public class ScriptsDialogView : ReactiveCoreWindow<ScriptsDialogViewModel>
{
private bool _canClose;
@@ -23,7 +22,7 @@ public partial class ScriptsDialogView : ReactiveCoreWindow<ScriptsDialogViewMod
{
if (_canClose)
return;
e.Cancel = true;
if (ViewModel == null || await ViewModel.CanClose())
{
@@ -13,23 +13,22 @@ using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.Scripting.Dialogs;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders;
using DynamicData;
using DynamicData.Binding;
using FluentAvalonia.UI.Controls;
using ReactiveUI;
using ContentDialogButton = Artemis.UI.Shared.Services.Builders.ContentDialogButton;
namespace Artemis.UI.Screens.Scripting;
public class ScriptsDialogViewModel : DialogViewModelBase<object?>
{
private readonly Dictionary<ScriptingProvider, IScriptEditorViewModel> _providerViewModels = new();
private readonly IScriptingService _scriptingService;
private readonly IWindowService _windowService;
private readonly Dictionary<ScriptingProvider, IScriptEditorViewModel> _providerViewModels = new();
private ObservableAsPropertyHelper<bool>? _hasScripts;
private ScriptConfigurationViewModel? _selectedScript;
private IScriptEditorViewModel? _scriptEditorViewModel;
private ReadOnlyObservableCollection<ScriptConfigurationViewModel> _scriptConfigurations;
private IScriptEditorViewModel? _scriptEditorViewModel;
private ScriptConfigurationViewModel? _selectedScript;
public ScriptsDialogViewModel(IScriptingService scriptingService, IWindowService windowService, IProfileService profileService, IScriptVmFactory scriptVmFactory)
{
@@ -96,6 +95,20 @@ public class ScriptsDialogViewModel : DialogViewModelBase<object?>
public ReactiveCommand<Unit, Unit> AddScriptConfiguration { get; }
public async Task<bool> CanClose()
{
if (!ScriptConfigurations.Any(s => s.ScriptConfiguration.HasChanges))
return true;
bool result = await _windowService.ShowConfirmContentDialog("Discard changes", "One or more scripts still have pending changes, do you want to discard them?");
if (!result)
return false;
foreach (ScriptConfigurationViewModel scriptConfigurationViewModel in ScriptConfigurations)
scriptConfigurationViewModel.ScriptConfiguration.DiscardPendingChanges();
return true;
}
private void SetupScriptEditor(ScriptConfiguration? scriptConfiguration)
{
@@ -144,18 +157,4 @@ public class ScriptsDialogViewModel : DialogViewModelBase<object?>
// Select the new script
SelectedScript = ScriptConfigurations.LastOrDefault();
}
public async Task<bool> CanClose()
{
if (!ScriptConfigurations.Any(s => s.ScriptConfiguration.HasChanges))
return true;
bool result = await _windowService.ShowConfirmContentDialog("Discard changes", "One or more scripts still have pending changes, do you want to discard them?");
if (!result)
return false;
foreach (ScriptConfigurationViewModel scriptConfigurationViewModel in ScriptConfigurations)
scriptConfigurationViewModel.ScriptConfiguration.DiscardPendingChanges();
return true;
}
}
@@ -1,18 +1,17 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Settings
{
public class SettingsView : ReactiveUserControl<SettingsViewModel>
{
public SettingsView()
{
InitializeComponent();
}
namespace Artemis.UI.Screens.Settings;
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public class SettingsView : ReactiveUserControl<SettingsViewModel>
{
public SettingsView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
@@ -2,25 +2,24 @@
using Artemis.UI.Shared;
using ReactiveUI;
namespace Artemis.UI.Screens.Settings
{
public class SettingsViewModel : MainScreenViewModel
{
public SettingsViewModel(IScreen hostScreen,
GeneralTabViewModel generalTabViewModel,
PluginsTabViewModel pluginsTabViewModel,
DevicesTabViewModel devicesTabViewModel,
AboutTabViewModel aboutTabViewModel) : base(hostScreen, "settings")
{
SettingTabs = new ObservableCollection<ActivatableViewModelBase>
{
generalTabViewModel,
pluginsTabViewModel,
devicesTabViewModel,
aboutTabViewModel
};
}
namespace Artemis.UI.Screens.Settings;
public ObservableCollection<ActivatableViewModelBase> SettingTabs { get; }
public class SettingsViewModel : MainScreenViewModel
{
public SettingsViewModel(IScreen hostScreen,
GeneralTabViewModel generalTabViewModel,
PluginsTabViewModel pluginsTabViewModel,
DevicesTabViewModel devicesTabViewModel,
AboutTabViewModel aboutTabViewModel) : base(hostScreen, "settings")
{
SettingTabs = new ObservableCollection<ActivatableViewModelBase>
{
generalTabViewModel,
pluginsTabViewModel,
devicesTabViewModel,
aboutTabViewModel
};
}
public ObservableCollection<ActivatableViewModelBase> SettingTabs { get; }
}
@@ -1,18 +1,17 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Settings
{
public partial class AboutTabView : ReactiveUserControl<AboutTabViewModel>
{
public AboutTabView()
{
InitializeComponent();
}
namespace Artemis.UI.Screens.Settings;
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public class AboutTabView : ReactiveUserControl<AboutTabViewModel>
{
public AboutTabView()
{
InitializeComponent();
}
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
@@ -8,69 +8,68 @@ using Avalonia.Media.Imaging;
using Flurl.Http;
using ReactiveUI;
namespace Artemis.UI.Screens.Settings
namespace Artemis.UI.Screens.Settings;
public class AboutTabViewModel : ActivatableViewModelBase
{
public class AboutTabViewModel : ActivatableViewModelBase
private Bitmap? _darthAffeProfileImage;
private Bitmap? _drMeteorProfileImage;
private Bitmap? _kaiProfileImage;
private Bitmap? _robertProfileImage;
private string? _version;
public AboutTabViewModel()
{
private Bitmap? _darthAffeProfileImage;
private Bitmap? _drMeteorProfileImage;
private Bitmap? _kaiProfileImage;
private Bitmap? _robertProfileImage;
private string? _version;
DisplayName = "About";
this.WhenActivated((CompositeDisposable _) => Task.Run(Activate));
}
public AboutTabViewModel()
public string? Version
{
get => _version;
set => RaiseAndSetIfChanged(ref _version, value);
}
public Bitmap? RobertProfileImage
{
get => _robertProfileImage;
set => RaiseAndSetIfChanged(ref _robertProfileImage, value);
}
public Bitmap? DarthAffeProfileImage
{
get => _darthAffeProfileImage;
set => RaiseAndSetIfChanged(ref _darthAffeProfileImage, value);
}
public Bitmap? DrMeteorProfileImage
{
get => _drMeteorProfileImage;
set => RaiseAndSetIfChanged(ref _drMeteorProfileImage, value);
}
public Bitmap? KaiProfileImage
{
get => _kaiProfileImage;
set => RaiseAndSetIfChanged(ref _kaiProfileImage, value);
}
private async Task Activate()
{
AssemblyInformationalVersionAttribute? versionAttribute = typeof(AboutTabViewModel).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
Version = $"Version {versionAttribute?.InformationalVersion} build {Constants.BuildInfo.BuildNumberDisplay}";
try
{
DisplayName = "About";
this.WhenActivated((CompositeDisposable _) => Task.Run(Activate));
RobertProfileImage = new Bitmap(await "https://avatars.githubusercontent.com/u/8858506".GetStreamAsync());
RobertProfileImage = new Bitmap(await "https://avatars.githubusercontent.com/u/8858506".GetStreamAsync());
DarthAffeProfileImage = new Bitmap(await "https://avatars.githubusercontent.com/u/1094841".GetStreamAsync());
DrMeteorProfileImage = new Bitmap(await "https://avatars.githubusercontent.com/u/29486064".GetStreamAsync());
KaiProfileImage = new Bitmap(await "https://i.imgur.com/8mPWY1j.png".GetStreamAsync());
}
public string? Version
catch (Exception)
{
get => _version;
set => RaiseAndSetIfChanged(ref _version, value);
}
public Bitmap? RobertProfileImage
{
get => _robertProfileImage;
set => RaiseAndSetIfChanged(ref _robertProfileImage, value);
}
public Bitmap? DarthAffeProfileImage
{
get => _darthAffeProfileImage;
set => RaiseAndSetIfChanged(ref _darthAffeProfileImage, value);
}
public Bitmap? DrMeteorProfileImage
{
get => _drMeteorProfileImage;
set => RaiseAndSetIfChanged(ref _drMeteorProfileImage, value);
}
public Bitmap? KaiProfileImage
{
get => _kaiProfileImage;
set => RaiseAndSetIfChanged(ref _kaiProfileImage, value);
}
private async Task Activate()
{
AssemblyInformationalVersionAttribute? versionAttribute = typeof(AboutTabViewModel).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
Version = $"Version {versionAttribute?.InformationalVersion} build {Constants.BuildInfo.BuildNumberDisplay}";
try
{
RobertProfileImage = new Bitmap(await "https://avatars.githubusercontent.com/u/8858506".GetStreamAsync());
RobertProfileImage = new Bitmap(await "https://avatars.githubusercontent.com/u/8858506".GetStreamAsync());
DarthAffeProfileImage = new Bitmap(await "https://avatars.githubusercontent.com/u/1094841".GetStreamAsync());
DrMeteorProfileImage = new Bitmap(await "https://avatars.githubusercontent.com/u/29486064".GetStreamAsync());
KaiProfileImage = new Bitmap(await "https://i.imgur.com/8mPWY1j.png".GetStreamAsync());
}
catch (Exception)
{
// ignored, unluckyyyy
}
// ignored, unluckyyyy
}
}
}
@@ -1,18 +1,17 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Settings
{
public class DevicesTabView : ReactiveUserControl<DevicesTabViewModel>
{
public DevicesTabView()
{
InitializeComponent();
}
namespace Artemis.UI.Screens.Settings;
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public class DevicesTabView : ReactiveUserControl<DevicesTabViewModel>
{
public DevicesTabView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
@@ -14,82 +14,78 @@ using Avalonia.Threading;
using DynamicData;
using ReactiveUI;
namespace Artemis.UI.Screens.Settings
namespace Artemis.UI.Screens.Settings;
public class DevicesTabViewModel : ActivatableViewModelBase
{
public class DevicesTabViewModel : ActivatableViewModelBase
private readonly IDeviceVmFactory _deviceVmFactory;
private readonly IRgbService _rgbService;
private readonly IWindowService _windowService;
private bool _confirmedDisable;
public DevicesTabViewModel(IRgbService rgbService, IWindowService windowService, IDeviceVmFactory deviceVmFactory)
{
private readonly IDeviceVmFactory _deviceVmFactory;
private readonly IRgbService _rgbService;
private readonly IWindowService _windowService;
private bool _confirmedDisable;
DisplayName = "Devices";
public DevicesTabViewModel(IRgbService rgbService, IWindowService windowService, IDeviceVmFactory deviceVmFactory)
_rgbService = rgbService;
_windowService = windowService;
_deviceVmFactory = deviceVmFactory;
Devices = new ObservableCollection<DeviceSettingsViewModel>();
this.WhenActivated(disposables =>
{
DisplayName = "Devices";
GetDevices();
_rgbService = rgbService;
_windowService = windowService;
_deviceVmFactory = deviceVmFactory;
Observable.FromEventPattern<DeviceEventArgs>(x => rgbService.DeviceAdded += x, x => rgbService.DeviceAdded -= x)
.Subscribe(d => AddDevice(d.EventArgs.Device))
.DisposeWith(disposables);
Observable.FromEventPattern<DeviceEventArgs>(x => rgbService.DeviceRemoved += x, x => rgbService.DeviceRemoved -= x)
.Subscribe(d => RemoveDevice(d.EventArgs.Device))
.DisposeWith(disposables);
});
}
Devices = new ObservableCollection<DeviceSettingsViewModel>();
this.WhenActivated(disposables =>
{
GetDevices();
public ObservableCollection<DeviceSettingsViewModel> Devices { get; }
Observable.FromEventPattern<DeviceEventArgs>(x => rgbService.DeviceAdded += x, x => rgbService.DeviceAdded -= x)
.Subscribe(d => AddDevice(d.EventArgs.Device))
.DisposeWith(disposables);
Observable.FromEventPattern<DeviceEventArgs>(x => rgbService.DeviceRemoved += x, x => rgbService.DeviceRemoved -= x)
.Subscribe(d => RemoveDevice(d.EventArgs.Device))
.DisposeWith(disposables);
});
}
public async Task<bool> ShowDeviceDisableDialog()
{
if (_confirmedDisable)
return true;
private void GetDevices()
{
Devices.Clear();
Dispatcher.UIThread.InvokeAsync(() =>
{
Devices.AddRange(_rgbService.Devices.Select(d => _deviceVmFactory.DeviceSettingsViewModel(d, this)));
}, DispatcherPriority.Background);
}
bool confirmed = await _windowService.ShowConfirmContentDialog(
"Disabling device",
"Disabling a device will cause it to stop updating. " +
"\r\nSome SDKs will even go back to using manufacturer lighting (Artemis restart may be required)."
);
if (confirmed)
_confirmedDisable = true;
public ObservableCollection<DeviceSettingsViewModel> Devices { get; }
return confirmed;
}
public async Task<bool> ShowDeviceDisableDialog()
{
if (_confirmedDisable)
return true;
private void GetDevices()
{
Devices.Clear();
Dispatcher.UIThread.InvokeAsync(() => { Devices.AddRange(_rgbService.Devices.Select(d => _deviceVmFactory.DeviceSettingsViewModel(d, this))); }, DispatcherPriority.Background);
}
bool confirmed = await _windowService.ShowConfirmContentDialog(
"Disabling device",
"Disabling a device will cause it to stop updating. " +
"\r\nSome SDKs will even go back to using manufacturer lighting (Artemis restart may be required)."
);
if (confirmed)
_confirmedDisable = true;
private void AddDevice(ArtemisDevice device)
{
// If the device was only enabled, don't add it
if (Devices.Any(d => d.Device == device))
return;
return confirmed;
}
Devices.Add(_deviceVmFactory.DeviceSettingsViewModel(device, this));
}
private void AddDevice(ArtemisDevice device)
{
// If the device was only enabled, don't add it
if (Devices.Any(d => d.Device == device))
return;
private void RemoveDevice(ArtemisDevice device)
{
// If the device was only disabled don't remove it
if (_rgbService.Devices.Contains(device))
return;
Devices.Add(_deviceVmFactory.DeviceSettingsViewModel(device, this));
}
private void RemoveDevice(ArtemisDevice device)
{
// If the device was only disabled don't remove it
if (_rgbService.Devices.Contains(device))
return;
DeviceSettingsViewModel? viewModel = Devices.FirstOrDefault(i => i.Device == device);
if (viewModel != null)
Devices.Remove(viewModel);
}
DeviceSettingsViewModel? viewModel = Devices.FirstOrDefault(i => i.Device == device);
if (viewModel != null)
Devices.Remove(viewModel);
}
}
@@ -1,18 +1,17 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Settings
{
public partial class GeneralTabView : ReactiveUserControl<GeneralTabViewModel>
{
public GeneralTabView()
{
InitializeComponent();
}
namespace Artemis.UI.Screens.Settings;
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public class GeneralTabView : ReactiveUserControl<GeneralTabViewModel>
{
public GeneralTabView()
{
InitializeComponent();
}
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
@@ -22,199 +21,198 @@ using Ninject;
using ReactiveUI;
using Serilog.Events;
namespace Artemis.UI.Screens.Settings
namespace Artemis.UI.Screens.Settings;
public class GeneralTabViewModel : ActivatableViewModelBase
{
public class GeneralTabViewModel : ActivatableViewModelBase
private readonly IAutoRunProvider? _autoRunProvider;
private readonly IDebugService _debugService;
private readonly PluginSetting<LayerBrushReference> _defaultLayerBrushDescriptor;
private readonly ISettingsService _settingsService;
private readonly IUpdateService _updateService;
private readonly IWindowService _windowService;
private bool _startupWizardOpen;
public GeneralTabViewModel(IKernel kernel,
ISettingsService settingsService,
IPluginManagementService pluginManagementService,
IDebugService debugService,
IWindowService windowService,
IUpdateService updateService)
{
private readonly PluginSetting<LayerBrushReference> _defaultLayerBrushDescriptor;
private readonly ISettingsService _settingsService;
private readonly IDebugService _debugService;
private readonly IWindowService _windowService;
private readonly IUpdateService _updateService;
private readonly IAutoRunProvider? _autoRunProvider;
private bool _startupWizardOpen;
DisplayName = "General";
_settingsService = settingsService;
_debugService = debugService;
_windowService = windowService;
_updateService = updateService;
_autoRunProvider = kernel.TryGet<IAutoRunProvider>();
public GeneralTabViewModel(IKernel kernel,
ISettingsService settingsService,
IPluginManagementService pluginManagementService,
IDebugService debugService,
IWindowService windowService,
IUpdateService updateService)
List<LayerBrushProvider> layerBrushProviders = pluginManagementService.GetFeaturesOfType<LayerBrushProvider>();
List<IGraphicsContextProvider> graphicsContextProviders = kernel.Get<List<IGraphicsContextProvider>>();
LayerBrushDescriptors = new ObservableCollection<LayerBrushDescriptor>(layerBrushProviders.SelectMany(l => l.LayerBrushDescriptors));
GraphicsContexts = new ObservableCollection<string> {"Software"};
GraphicsContexts.AddRange(graphicsContextProviders.Select(p => p.GraphicsContextName));
_defaultLayerBrushDescriptor = _settingsService.GetSetting("ProfileEditor.DefaultLayerBrushDescriptor", new LayerBrushReference
{
DisplayName = "General";
_settingsService = settingsService;
_debugService = debugService;
_windowService = windowService;
_updateService = updateService;
_autoRunProvider = kernel.TryGet<IAutoRunProvider>();
LayerBrushProviderId = "Artemis.Plugins.LayerBrushes.Color.ColorBrushProvider-92a9d6ba",
BrushType = "SolidBrush"
});
List<LayerBrushProvider> layerBrushProviders = pluginManagementService.GetFeaturesOfType<LayerBrushProvider>();
List<IGraphicsContextProvider> graphicsContextProviders = kernel.Get<List<IGraphicsContextProvider>>();
LayerBrushDescriptors = new ObservableCollection<LayerBrushDescriptor>(layerBrushProviders.SelectMany(l => l.LayerBrushDescriptors));
GraphicsContexts = new ObservableCollection<string> {"Software"};
GraphicsContexts.AddRange(graphicsContextProviders.Select(p => p.GraphicsContextName));
ShowLogs = ReactiveCommand.Create(ExecuteShowLogs);
CheckForUpdate = ReactiveCommand.CreateFromTask(ExecuteCheckForUpdate);
ShowSetupWizard = ReactiveCommand.CreateFromTask(ExecuteShowSetupWizard);
ShowDebugger = ReactiveCommand.Create(ExecuteShowDebugger);
ShowDataFolder = ReactiveCommand.Create(ExecuteShowDataFolder);
_defaultLayerBrushDescriptor = _settingsService.GetSetting("ProfileEditor.DefaultLayerBrushDescriptor", new LayerBrushReference
this.WhenActivated(d =>
{
UIAutoRun.SettingChanged += UIAutoRunOnSettingChanged;
UIAutoRunDelay.SettingChanged += UIAutoRunDelayOnSettingChanged;
Dispatcher.UIThread.InvokeAsync(ApplyAutoRun);
Disposable.Create(() =>
{
LayerBrushProviderId = "Artemis.Plugins.LayerBrushes.Color.ColorBrushProvider-92a9d6ba",
BrushType = "SolidBrush"
});
UIAutoRun.SettingChanged -= UIAutoRunOnSettingChanged;
UIAutoRunDelay.SettingChanged -= UIAutoRunDelayOnSettingChanged;
ShowLogs = ReactiveCommand.Create(ExecuteShowLogs);
CheckForUpdate = ReactiveCommand.CreateFromTask(ExecuteCheckForUpdate);
ShowSetupWizard = ReactiveCommand.CreateFromTask(ExecuteShowSetupWizard);
ShowDebugger = ReactiveCommand.Create(ExecuteShowDebugger);
ShowDataFolder = ReactiveCommand.Create(ExecuteShowDataFolder);
_settingsService.SaveAllSettings();
}).DisposeWith(d);
});
}
this.WhenActivated(d =>
{
UIAutoRun.SettingChanged += UIAutoRunOnSettingChanged;
UIAutoRunDelay.SettingChanged += UIAutoRunDelayOnSettingChanged;
public ReactiveCommand<Unit, Unit> ShowLogs { get; }
public ReactiveCommand<Unit, Unit> CheckForUpdate { get; }
public ReactiveCommand<Unit, Unit> ShowSetupWizard { get; }
public ReactiveCommand<Unit, Unit> ShowDebugger { get; }
public ReactiveCommand<Unit, Unit> ShowDataFolder { get; }
Dispatcher.UIThread.InvokeAsync(ApplyAutoRun);
Disposable.Create(() =>
{
UIAutoRun.SettingChanged -= UIAutoRunOnSettingChanged;
UIAutoRunDelay.SettingChanged -= UIAutoRunDelayOnSettingChanged;
_settingsService.SaveAllSettings();
}).DisposeWith(d);
});
public bool IsAutoRunSupported => _autoRunProvider != null;
public bool IsUpdatingSupported => _updateService.UpdatingSupported;
public ObservableCollection<LayerBrushDescriptor> LayerBrushDescriptors { get; }
public ObservableCollection<string> GraphicsContexts { get; }
public ObservableCollection<RenderSettingViewModel> RenderScales { get; } = new()
{
new RenderSettingViewModel("25%", 0.25),
new RenderSettingViewModel("50%", 0.5),
new RenderSettingViewModel("100%", 1)
};
public ObservableCollection<RenderSettingViewModel> TargetFrameRates { get; } = new()
{
new RenderSettingViewModel("10 FPS", 10),
new RenderSettingViewModel("20 FPS", 20),
new RenderSettingViewModel("30 FPS", 30),
new RenderSettingViewModel("45 FPS", 45),
new RenderSettingViewModel("60 FPS (lol)", 60),
new RenderSettingViewModel("144 FPS (omegalol)", 144)
};
public LayerBrushDescriptor? SelectedLayerBrushDescriptor
{
get => LayerBrushDescriptors.FirstOrDefault(d => d.MatchesLayerBrushReference(_defaultLayerBrushDescriptor.Value));
set
{
if (value != null) _defaultLayerBrushDescriptor.Value = new LayerBrushReference(value);
}
}
public ReactiveCommand<Unit, Unit> ShowLogs { get; }
public ReactiveCommand<Unit, Unit> CheckForUpdate { get; }
public ReactiveCommand<Unit, Unit> ShowSetupWizard { get; }
public ReactiveCommand<Unit, Unit> ShowDebugger { get; }
public ReactiveCommand<Unit, Unit> ShowDataFolder { get; }
public bool IsAutoRunSupported => _autoRunProvider != null;
public bool IsUpdatingSupported => _updateService.UpdatingSupported;
public ObservableCollection<LayerBrushDescriptor> LayerBrushDescriptors { get; }
public ObservableCollection<string> GraphicsContexts { get; }
public ObservableCollection<RenderSettingViewModel> RenderScales { get; } = new()
public RenderSettingViewModel? SelectedRenderScale
{
get => RenderScales.FirstOrDefault(s => Math.Abs(s.Value - CoreRenderScale.Value) < 0.01);
set
{
new RenderSettingViewModel("25%", 0.25),
new RenderSettingViewModel("50%", 0.5),
new RenderSettingViewModel("100%", 1)
};
public ObservableCollection<RenderSettingViewModel> TargetFrameRates { get; } = new()
{
new RenderSettingViewModel("10 FPS", 10),
new RenderSettingViewModel("20 FPS", 20),
new RenderSettingViewModel("30 FPS", 30),
new RenderSettingViewModel("45 FPS", 45),
new RenderSettingViewModel("60 FPS (lol)", 60),
new RenderSettingViewModel("144 FPS (omegalol)", 144)
};
public LayerBrushDescriptor? SelectedLayerBrushDescriptor
{
get => LayerBrushDescriptors.FirstOrDefault(d => d.MatchesLayerBrushReference(_defaultLayerBrushDescriptor.Value));
set
{
if (value != null) _defaultLayerBrushDescriptor.Value = new LayerBrushReference(value);
}
if (value != null)
CoreRenderScale.Value = value.Value;
}
}
public RenderSettingViewModel? SelectedRenderScale
public RenderSettingViewModel? SelectedTargetFrameRate
{
get => TargetFrameRates.FirstOrDefault(s => Math.Abs(s.Value - CoreTargetFrameRate.Value) < 0.01);
set
{
get => RenderScales.FirstOrDefault(s => Math.Abs(s.Value - CoreRenderScale.Value) < 0.01);
set
{
if (value != null)
CoreRenderScale.Value = value.Value;
}
if (value != null)
CoreTargetFrameRate.Value = (int) value.Value;
}
}
public RenderSettingViewModel? SelectedTargetFrameRate
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 PluginSetting<bool> UIAutoUpdate => _settingsService.GetSetting("UI.AutoUpdate", false);
public PluginSetting<bool> ProfileEditorShowDataModelValues => _settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false);
public PluginSetting<LogEventLevel> CoreLoggingLevel => _settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Information);
public PluginSetting<string> CorePreferredGraphicsContext => _settingsService.GetSetting("Core.PreferredGraphicsContext", "Software");
public PluginSetting<double> CoreRenderScale => _settingsService.GetSetting("Core.RenderScale", 0.25);
public PluginSetting<int> CoreTargetFrameRate => _settingsService.GetSetting("Core.TargetFrameRate", 30);
public PluginSetting<int> WebServerPort => _settingsService.GetSetting("WebServer.Port", 9696);
private void ExecuteShowLogs()
{
Utilities.OpenFolder(Constants.LogsFolder);
}
private async Task ExecuteCheckForUpdate(CancellationToken cancellationToken)
{
await _updateService.ManualUpdate();
}
private async Task ExecuteShowSetupWizard()
{
_startupWizardOpen = true;
await _windowService.ShowDialogAsync<StartupWizardViewModel, bool>();
_startupWizardOpen = false;
}
private void ExecuteShowDebugger()
{
_debugService.ShowDebugger();
}
private void ExecuteShowDataFolder()
{
Utilities.OpenFolder(Constants.DataFolder);
}
private async Task ApplyAutoRun()
{
if (_autoRunProvider == null || _startupWizardOpen)
return;
try
{
get => TargetFrameRates.FirstOrDefault(s => Math.Abs(s.Value - CoreTargetFrameRate.Value) < 0.01);
set
{
if (value != null)
CoreTargetFrameRate.Value = (int) value.Value;
}
if (UIAutoRun.Value)
await _autoRunProvider.EnableAutoRun(false, UIAutoRunDelay.Value);
else
await _autoRunProvider.DisableAutoRun();
}
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 PluginSetting<bool> UIAutoUpdate => _settingsService.GetSetting("UI.AutoUpdate", false);
public PluginSetting<bool> ProfileEditorShowDataModelValues => _settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false);
public PluginSetting<LogEventLevel> CoreLoggingLevel => _settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Information);
public PluginSetting<string> CorePreferredGraphicsContext => _settingsService.GetSetting("Core.PreferredGraphicsContext", "Software");
public PluginSetting<double> CoreRenderScale => _settingsService.GetSetting("Core.RenderScale", 0.25);
public PluginSetting<int> CoreTargetFrameRate => _settingsService.GetSetting("Core.TargetFrameRate", 30);
public PluginSetting<int> WebServerPort => _settingsService.GetSetting("WebServer.Port", 9696);
private void ExecuteShowLogs()
catch (Exception exception)
{
Utilities.OpenFolder(Constants.LogsFolder);
_windowService.ShowExceptionDialog("Failed to apply auto-run", exception);
}
}
private async Task ExecuteCheckForUpdate(CancellationToken cancellationToken)
private async void UIAutoRunOnSettingChanged(object? sender, EventArgs e)
{
await ApplyAutoRun();
}
private async void UIAutoRunDelayOnSettingChanged(object? sender, EventArgs e)
{
if (_autoRunProvider == null || !UIAutoRun.Value || _startupWizardOpen)
return;
try
{
await _updateService.ManualUpdate();
await _autoRunProvider.EnableAutoRun(true, UIAutoRunDelay.Value);
}
private async Task ExecuteShowSetupWizard()
catch (Exception exception)
{
_startupWizardOpen = true;
await _windowService.ShowDialogAsync<StartupWizardViewModel, bool>();
_startupWizardOpen = false;
}
private void ExecuteShowDebugger()
{
_debugService.ShowDebugger();
}
private void ExecuteShowDataFolder()
{
Utilities.OpenFolder(Constants.DataFolder);
}
private async Task ApplyAutoRun()
{
if (_autoRunProvider == null || _startupWizardOpen)
return;
try
{
if (UIAutoRun.Value)
await _autoRunProvider.EnableAutoRun(false, UIAutoRunDelay.Value);
else
await _autoRunProvider.DisableAutoRun();
}
catch (Exception exception)
{
_windowService.ShowExceptionDialog("Failed to apply auto-run", exception);
}
}
private async void UIAutoRunOnSettingChanged(object? sender, EventArgs e)
{
await ApplyAutoRun();
}
private async void UIAutoRunDelayOnSettingChanged(object? sender, EventArgs e)
{
if (_autoRunProvider == null || !UIAutoRun.Value || _startupWizardOpen)
return;
try
{
await _autoRunProvider.EnableAutoRun(true, UIAutoRunDelay.Value);
}
catch (Exception exception)
{
_windowService.ShowExceptionDialog("Failed to apply auto-run", exception);
}
_windowService.ShowExceptionDialog("Failed to apply auto-run", exception);
}
}
}
@@ -1,18 +1,17 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Settings
{
public partial class PluginsTabView : ReactiveUserControl<PluginsTabViewModel>
{
public PluginsTabView()
{
InitializeComponent();
}
namespace Artemis.UI.Screens.Settings;
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public class PluginsTabView : ReactiveUserControl<PluginsTabViewModel>
{
public PluginsTabView()
{
InitializeComponent();
}
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
@@ -7,7 +7,6 @@ using System.Reactive.Linq;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Extensions;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.Plugins;
using Artemis.UI.Shared;
@@ -18,106 +17,105 @@ using DynamicData;
using DynamicData.Binding;
using ReactiveUI;
namespace Artemis.UI.Screens.Settings
namespace Artemis.UI.Screens.Settings;
public class PluginsTabViewModel : ActivatableViewModelBase
{
public class PluginsTabViewModel : ActivatableViewModelBase
private readonly INotificationService _notificationService;
private readonly IPluginManagementService _pluginManagementService;
private readonly IWindowService _windowService;
private string? _searchPluginInput;
public PluginsTabViewModel(IPluginManagementService pluginManagementService, INotificationService notificationService, IWindowService windowService, ISettingsVmFactory settingsVmFactory)
{
private readonly INotificationService _notificationService;
private readonly IPluginManagementService _pluginManagementService;
private readonly IWindowService _windowService;
private string? _searchPluginInput;
_pluginManagementService = pluginManagementService;
_notificationService = notificationService;
_windowService = windowService;
public PluginsTabViewModel(IPluginManagementService pluginManagementService, INotificationService notificationService, IWindowService windowService, ISettingsVmFactory settingsVmFactory)
DisplayName = "Plugins";
SourceList<Plugin> plugins = new();
IObservable<Func<Plugin, bool>> pluginFilter = this.WhenAnyValue(vm => vm.SearchPluginInput).Throttle(TimeSpan.FromMilliseconds(100)).Select(CreatePredicate);
plugins.Connect()
.Filter(pluginFilter)
.Sort(SortExpressionComparer<Plugin>.Ascending(p => p.Info.Name))
.Transform(settingsVmFactory.PluginSettingsViewModel)
.ObserveOn(AvaloniaScheduler.Instance)
.Bind(out ReadOnlyObservableCollection<PluginSettingsViewModel> pluginViewModels)
.Subscribe();
Plugins = pluginViewModels;
this.WhenActivated(d =>
{
_pluginManagementService = pluginManagementService;
_notificationService = notificationService;
_windowService = windowService;
Dispatcher.UIThread.Post(() => plugins.AddRange(_pluginManagementService.GetAllPlugins()), DispatcherPriority.Background);
Observable.FromEventPattern<PluginEventArgs>(x => _pluginManagementService.PluginLoaded += x, x => _pluginManagementService.PluginLoaded -= x)
.Subscribe(a => plugins.Add(a.EventArgs.Plugin))
.DisposeWith(d);
Observable.FromEventPattern<PluginEventArgs>(x => _pluginManagementService.PluginUnloaded += x, x => _pluginManagementService.PluginUnloaded -= x)
.Subscribe(a => plugins.Remove(a.EventArgs.Plugin))
.DisposeWith(d);
Disposable.Create(() => plugins.Clear()).DisposeWith(d);
});
ImportPlugin = ReactiveCommand.CreateFromTask(ExecuteImportPlugin);
}
DisplayName = "Plugins";
public ReadOnlyObservableCollection<PluginSettingsViewModel> Plugins { get; }
public ReactiveCommand<Unit, Unit> ImportPlugin { get; }
SourceList<Plugin> plugins = new();
IObservable<Func<Plugin, bool>> pluginFilter = this.WhenAnyValue(vm => vm.SearchPluginInput).Throttle(TimeSpan.FromMilliseconds(100)).Select(CreatePredicate);
public string? SearchPluginInput
{
get => _searchPluginInput;
set => RaiseAndSetIfChanged(ref _searchPluginInput, value);
}
plugins.Connect()
.Filter(pluginFilter)
.Sort(SortExpressionComparer<Plugin>.Ascending(p => p.Info.Name))
.Transform(settingsVmFactory.PluginSettingsViewModel)
.ObserveOn(AvaloniaScheduler.Instance)
.Bind(out ReadOnlyObservableCollection<PluginSettingsViewModel> pluginViewModels)
.Subscribe();
Plugins = pluginViewModels;
public void OpenUrl(string url)
{
Utilities.OpenUrl(url);
}
this.WhenActivated(d =>
private async Task ExecuteImportPlugin()
{
string[]? files = await _windowService.CreateOpenFileDialog().WithTitle("Import Artemis plugin").HavingFilter(f => f.WithExtension("zip").WithName("ZIP files")).ShowAsync();
if (files == null)
return;
try
{
Plugin? plugin = _pluginManagementService.ImportPlugin(files[0]);
if (plugin == null)
{
Dispatcher.UIThread.Post(() => plugins.AddRange(_pluginManagementService.GetAllPlugins()), DispatcherPriority.Background);
Observable.FromEventPattern<PluginEventArgs>(x => _pluginManagementService.PluginLoaded += x, x => _pluginManagementService.PluginLoaded -= x)
.Subscribe(a => plugins.Add(a.EventArgs.Plugin))
.DisposeWith(d);
Observable.FromEventPattern<PluginEventArgs>(x => _pluginManagementService.PluginUnloaded += x, x => _pluginManagementService.PluginUnloaded -= x)
.Subscribe(a => plugins.Remove(a.EventArgs.Plugin))
.DisposeWith(d);
Disposable.Create(() => plugins.Clear()).DisposeWith(d);
});
ImportPlugin = ReactiveCommand.CreateFromTask(ExecuteImportPlugin);
}
public ReadOnlyObservableCollection<PluginSettingsViewModel> Plugins { get; }
public ReactiveCommand<Unit, Unit> ImportPlugin { get; }
public string? SearchPluginInput
{
get => _searchPluginInput;
set => RaiseAndSetIfChanged(ref _searchPluginInput, value);
}
public void OpenUrl(string url)
{
Utilities.OpenUrl(url);
}
private async Task ExecuteImportPlugin()
{
string[]? files = await _windowService.CreateOpenFileDialog().WithTitle("Import Artemis plugin").HavingFilter(f => f.WithExtension("zip").WithName("ZIP files")).ShowAsync();
if (files == null)
await _windowService.ShowConfirmContentDialog("Failed to import plugin", "Make sure it is compatible with the current platform.", "Close", null);
return;
try
{
Plugin? plugin = _pluginManagementService.ImportPlugin(files[0]);
if (plugin == null)
{
await _windowService.ShowConfirmContentDialog("Failed to import plugin", "Make sure it is compatible with the current platform.", "Close", null);
return;
}
SearchPluginInput = plugin.Info.Name;
// 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? settingsViewModel = Plugins.FirstOrDefault(i => i.PluginViewModel.Plugin == plugin);
if (settingsViewModel != null && !settingsViewModel.PluginViewModel.IsEnabled)
await settingsViewModel.PluginViewModel.UpdateEnabled(true);
_notificationService.CreateNotification()
.WithTitle("Plugin imported")
.WithMessage($"Added the '{plugin.Info.Name}' plugin")
.WithSeverity(NotificationSeverity.Success)
.Show();
}
catch (Exception e)
{
await _windowService.ShowConfirmContentDialog("Failed to import plugin", "Make sure the selected ZIP file is a valid Artemis plugin.\r\n" + e.Message, "Close", null);
}
SearchPluginInput = plugin.Info.Name;
// 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? settingsViewModel = Plugins.FirstOrDefault(i => i.PluginViewModel.Plugin == plugin);
if (settingsViewModel != null && !settingsViewModel.PluginViewModel.IsEnabled)
await settingsViewModel.PluginViewModel.UpdateEnabled(true);
_notificationService.CreateNotification()
.WithTitle("Plugin imported")
.WithMessage($"Added the '{plugin.Info.Name}' plugin")
.WithSeverity(NotificationSeverity.Success)
.Show();
}
private Func<Plugin, bool> CreatePredicate(string? text)
catch (Exception e)
{
if (string.IsNullOrWhiteSpace(text))
return _ => true;
return data => data.Info.Name.Contains(text, StringComparison.InvariantCultureIgnoreCase) ||
(data.Info.Description != null && data.Info.Description.Contains(text, StringComparison.InvariantCultureIgnoreCase));
await _windowService.ShowConfirmContentDialog("Failed to import plugin", "Make sure the selected ZIP file is a valid Artemis plugin.\r\n" + e.Message, "Close", null);
}
}
private Func<Plugin, bool> CreatePredicate(string? text)
{
if (string.IsNullOrWhiteSpace(text))
return _ => true;
return data => data.Info.Name.Contains(text, StringComparison.InvariantCultureIgnoreCase) ||
(data.Info.Description != null && data.Info.Description.Contains(text, StringComparison.InvariantCultureIgnoreCase));
}
}
@@ -1,13 +1,7 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Artemis.Core;
using Artemis.UI.Screens.ProfileEditor.ProfileTree;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Generators;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
using Avalonia.Xaml.Interactions.DragAndDrop;
@@ -15,6 +9,30 @@ namespace Artemis.UI.Screens.Sidebar.Behaviors;
public class SidebarCategoryViewDropHandler : DropHandlerBase
{
public override bool Validate(object? sender, DragEventArgs e, object? sourceContext, object? targetContext, object? state)
{
if (sender is ItemsControl itemsControl)
foreach (ItemContainerInfo? item in itemsControl.ItemContainerGenerator.Containers)
SetDraggingPseudoClasses(item.ContainerControl, false, false);
if (e.Source is IControl && sender is ListBox listBox)
return Validate(listBox, e, sourceContext, targetContext, false);
return false;
}
public override bool Execute(object? sender, DragEventArgs e, object? sourceContext, object? targetContext, object? state)
{
if (sender is ItemsControl itemsControl)
foreach (ItemContainerInfo? item in itemsControl.ItemContainerGenerator.Containers)
SetDraggingPseudoClasses(item.ContainerControl, false, false);
if (e.Source is IControl && sender is ListBox listBox)
return Validate(listBox, e, sourceContext, targetContext, true);
return false;
}
private bool Validate(ListBox listBox, DragEventArgs e, object? sourceContext, object? targetContext, bool bExecute)
{
if (sourceContext is not SidebarProfileConfigurationViewModel sourceItem || targetContext is not SidebarCategoryViewModel vm ||
@@ -33,7 +51,7 @@ public class SidebarCategoryViewDropHandler : DropHandlerBase
if (targetVisual != null)
{
Point positionInTarget = e.GetPosition(targetVisual);
if (positionInTarget.Y > (targetVisual.Bounds.Height / 2))
if (positionInTarget.Y > targetVisual.Bounds.Height / 2)
before = false;
}
}
@@ -63,38 +81,6 @@ public class SidebarCategoryViewDropHandler : DropHandlerBase
return true;
}
public override bool Validate(object? sender, DragEventArgs e, object? sourceContext, object? targetContext, object? state)
{
if (sender is ItemsControl itemsControl)
{
foreach (ItemContainerInfo? item in itemsControl.ItemContainerGenerator.Containers)
SetDraggingPseudoClasses(item.ContainerControl, false, false);
}
if (e.Source is IControl && sender is ListBox listBox)
{
return Validate(listBox, e, sourceContext, targetContext, false);
}
return false;
}
public override bool Execute(object? sender, DragEventArgs e, object? sourceContext, object? targetContext, object? state)
{
if (sender is ItemsControl itemsControl)
{
foreach (ItemContainerInfo? item in itemsControl.ItemContainerGenerator.Containers)
SetDraggingPseudoClasses(item.ContainerControl, false, false);
}
if (e.Source is IControl && sender is ListBox listBox)
{
return Validate(listBox, e, sourceContext, targetContext, true);
}
return false;
}
private void SetDraggingPseudoClasses(IControl control, bool dragging, bool before)
{
if (!dragging)
@@ -3,19 +3,18 @@ using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using ReactiveUI;
namespace Artemis.UI.Screens.Sidebar
{
public class SidebarCategoryEditView : ReactiveUserControl<SidebarCategoryEditViewModel>
{
public SidebarCategoryEditView()
{
InitializeComponent();
this.WhenActivated(_ => this.ClearAllDataValidationErrors());
}
namespace Artemis.UI.Screens.Sidebar;
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public class SidebarCategoryEditView : ReactiveUserControl<SidebarCategoryEditViewModel>
{
public SidebarCategoryEditView()
{
InitializeComponent();
this.WhenActivated(_ => this.ClearAllDataValidationErrors());
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
@@ -6,55 +6,56 @@ using FluentAvalonia.UI.Controls;
using ReactiveUI;
using ReactiveUI.Validation.Extensions;
namespace Artemis.UI.Screens.Sidebar
namespace Artemis.UI.Screens.Sidebar;
public class SidebarCategoryEditViewModel : ContentDialogViewModelBase
{
public class SidebarCategoryEditViewModel : ContentDialogViewModelBase
private readonly ProfileCategory? _category;
private readonly IProfileService _profileService;
private string? _categoryName;
public SidebarCategoryEditViewModel(IProfileService profileService, ProfileCategory? category)
{
private readonly IProfileService _profileService;
private readonly ProfileCategory? _category;
private string? _categoryName;
_profileService = profileService;
_category = category;
public SidebarCategoryEditViewModel(IProfileService profileService, ProfileCategory? category)
if (_category != null)
_categoryName = _category.Name;
Confirm = ReactiveCommand.Create(ExecuteConfirm, ValidationContext.Valid);
Delete = ReactiveCommand.Create(ExecuteDelete);
this.ValidationRule(vm => vm.CategoryName, categoryName => !string.IsNullOrWhiteSpace(categoryName), "You must specify a valid name");
}
public ReactiveCommand<Unit, Unit> Delete { get; set; }
public string? CategoryName
{
get => _categoryName;
set => RaiseAndSetIfChanged(ref _categoryName, value);
}
public ReactiveCommand<Unit, Unit> Confirm { get; }
private void ExecuteConfirm()
{
if (_category != null)
{
_profileService = profileService;
_category = category;
if (_category != null)
_categoryName = _category.Name;
Confirm = ReactiveCommand.Create(ExecuteConfirm, ValidationContext.Valid);
Delete = ReactiveCommand.Create(ExecuteDelete);
this.ValidationRule(vm => vm.CategoryName, categoryName => !string.IsNullOrWhiteSpace(categoryName), "You must specify a valid name");
_category.Name = CategoryName!;
_profileService.SaveProfileCategory(_category);
}
else
{
_profileService.CreateProfileCategory(CategoryName!);
}
public ReactiveCommand<Unit, Unit> Delete { get; set; }
ContentDialog?.Hide(ContentDialogResult.Primary);
}
public string? CategoryName
{
get => _categoryName;
set => this.RaiseAndSetIfChanged(ref _categoryName, value);
}
public ReactiveCommand<Unit, Unit> Confirm { get; }
private void ExecuteConfirm()
{
if (_category != null)
{
_category.Name = CategoryName!;
_profileService.SaveProfileCategory(_category);
}
else
_profileService.CreateProfileCategory(CategoryName!);
ContentDialog?.Hide(ContentDialogResult.Primary);
}
private void ExecuteDelete()
{
if (_category != null)
_profileService.DeleteProfileCategory(_category);
}
private void ExecuteDelete()
{
if (_category != null)
_profileService.DeleteProfileCategory(_category);
}
}
@@ -6,23 +6,22 @@ using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using ReactiveUI;
namespace Artemis.UI.Screens.Sidebar
{
public partial class ProfileConfigurationEditView : ReactiveCoreWindow<ProfileConfigurationEditViewModel>
{
public ProfileConfigurationEditView()
{
InitializeComponent();
this.WhenActivated(d => ViewModel.WhenAnyValue(vm => vm.SelectedBitmapSource).Subscribe(_ => this.Get<Border>("FillPreview").InvalidateVisual()).DisposeWith(d));
#if DEBUG
this.AttachDevTools();
#endif
}
namespace Artemis.UI.Screens.Sidebar;
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public class ProfileConfigurationEditView : ReactiveCoreWindow<ProfileConfigurationEditViewModel>
{
public ProfileConfigurationEditView()
{
InitializeComponent();
this.WhenActivated(d => ViewModel.WhenAnyValue(vm => vm.SelectedBitmapSource).Subscribe(_ => this.Get<Border>("FillPreview").InvalidateVisual()).DisposeWith(d));
#if DEBUG
this.AttachDevTools();
#endif
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
@@ -19,287 +19,284 @@ using Material.Icons;
using Newtonsoft.Json;
using ReactiveUI;
namespace Artemis.UI.Screens.Sidebar
namespace Artemis.UI.Screens.Sidebar;
public class ProfileConfigurationEditViewModel : DialogViewModelBase<ProfileConfiguration?>
{
public class ProfileConfigurationEditViewModel : DialogViewModelBase<ProfileConfiguration?>
private readonly ObservableAsPropertyHelper<ModuleActivationRequirementsViewModel?> _moduleActivationRequirementsViewModel;
private readonly ProfileCategory _profileCategory;
private readonly IProfileEditorService _profileEditorService;
private readonly IProfileService _profileService;
private readonly IWindowService _windowService;
private Hotkey? _disableHotkey;
private Hotkey? _enableHotkey;
private ProfileConfigurationHotkeyMode _hotkeyMode;
private ProfileConfigurationIconType _iconType;
private ObservableCollection<ProfileIconViewModel>? _materialIcons;
private ProfileConfiguration _profileConfiguration;
private string _profileName;
private Bitmap? _selectedBitmapSource;
private string? _selectedIconPath;
private ProfileIconViewModel? _selectedMaterialIcon;
private ProfileModuleViewModel? _selectedModule;
public ProfileConfigurationEditViewModel(
ProfileCategory profileCategory,
ProfileConfiguration? profileConfiguration,
IWindowService windowService,
IProfileService profileService,
IProfileEditorService profileEditorService,
IPluginManagementService pluginManagementService,
INodeVmFactory nodeVmFactory)
{
private readonly ProfileCategory _profileCategory;
private readonly IProfileService _profileService;
private readonly IProfileEditorService _profileEditorService;
private readonly IWindowService _windowService;
private Hotkey? _disableHotkey;
private Hotkey? _enableHotkey;
private ProfileConfigurationHotkeyMode _hotkeyMode;
private ProfileConfigurationIconType _iconType;
private ObservableCollection<ProfileIconViewModel>? _materialIcons;
private ProfileConfiguration _profileConfiguration;
private string _profileName;
private Bitmap? _selectedBitmapSource;
private string? _selectedIconPath;
private ProfileIconViewModel? _selectedMaterialIcon;
private ProfileModuleViewModel? _selectedModule;
private readonly ObservableAsPropertyHelper<ModuleActivationRequirementsViewModel?> _moduleActivationRequirementsViewModel;
_profileCategory = profileCategory;
_windowService = windowService;
_profileService = profileService;
_profileEditorService = profileEditorService;
_profileConfiguration = profileConfiguration ?? profileService.CreateProfileConfiguration(profileCategory, "New profile", Enum.GetValues<MaterialIconKind>().First().ToString());
_profileName = _profileConfiguration.Name;
_iconType = _profileConfiguration.Icon.IconType;
_hotkeyMode = _profileConfiguration.HotkeyMode;
if (_profileConfiguration.EnableHotkey != null)
_enableHotkey = new Hotkey {Key = _profileConfiguration.EnableHotkey.Key, Modifiers = _profileConfiguration.EnableHotkey.Modifiers};
if (_profileConfiguration.DisableHotkey != null)
_disableHotkey = new Hotkey {Key = _profileConfiguration.DisableHotkey.Key, Modifiers = _profileConfiguration.DisableHotkey.Modifiers};
public ProfileConfigurationEditViewModel(
ProfileCategory profileCategory,
ProfileConfiguration? profileConfiguration,
IWindowService windowService,
IProfileService profileService,
IProfileEditorService profileEditorService,
IPluginManagementService pluginManagementService,
INodeVmFactory nodeVmFactory)
IsNew = profileConfiguration == null;
DisplayName = IsNew ? "Artemis | Add profile" : "Artemis | Edit profile";
Modules = new ObservableCollection<ProfileModuleViewModel?>(
pluginManagementService.GetFeaturesOfType<Module>().Where(m => !m.IsAlwaysAvailable).Select(m => new ProfileModuleViewModel(m))
);
Modules.Insert(0, null);
_selectedModule = Modules.FirstOrDefault(m => m?.Module == _profileConfiguration.Module);
VisualEditorViewModel = nodeVmFactory.NodeScriptViewModel(_profileConfiguration.ActivationCondition, true);
BrowseBitmapFile = ReactiveCommand.CreateFromTask(ExecuteBrowseBitmapFile);
OpenConditionEditor = ReactiveCommand.CreateFromTask(ExecuteOpenConditionEditor);
Confirm = ReactiveCommand.CreateFromTask(ExecuteConfirm);
Import = ReactiveCommand.CreateFromTask(ExecuteImport);
Delete = ReactiveCommand.CreateFromTask(ExecuteDelete);
Cancel = ReactiveCommand.Create(ExecuteCancel);
_moduleActivationRequirementsViewModel = this.WhenAnyValue(vm => vm.SelectedModule)
.Select(m => m != null ? new ModuleActivationRequirementsViewModel(m.Module) : null)
.ToProperty(this, vm => vm.ModuleActivationRequirementsViewModel);
Dispatcher.UIThread.Post(LoadIcon, DispatcherPriority.Background);
}
public bool IsNew { get; }
public ProfileConfiguration ProfileConfiguration
{
get => _profileConfiguration;
set => RaiseAndSetIfChanged(ref _profileConfiguration, value);
}
public string ProfileName
{
get => _profileName;
set => RaiseAndSetIfChanged(ref _profileName, value);
}
public ProfileConfigurationHotkeyMode HotkeyMode
{
get => _hotkeyMode;
set => RaiseAndSetIfChanged(ref _hotkeyMode, value);
}
public Hotkey? EnableHotkey
{
get => _enableHotkey;
set => RaiseAndSetIfChanged(ref _enableHotkey, value);
}
public Hotkey? DisableHotkey
{
get => _disableHotkey;
set => RaiseAndSetIfChanged(ref _disableHotkey, value);
}
public ObservableCollection<ProfileModuleViewModel?> Modules { get; }
public ProfileModuleViewModel? SelectedModule
{
get => _selectedModule;
set => RaiseAndSetIfChanged(ref _selectedModule, value);
}
public NodeScriptViewModel VisualEditorViewModel { get; }
public ModuleActivationRequirementsViewModel? ModuleActivationRequirementsViewModel => _moduleActivationRequirementsViewModel.Value;
public ReactiveCommand<Unit, Unit> OpenConditionEditor { get; }
public ReactiveCommand<Unit, Unit> BrowseBitmapFile { get; }
public ReactiveCommand<Unit, Unit> Confirm { get; }
public ReactiveCommand<Unit, Unit> Import { get; }
public ReactiveCommand<Unit, Unit> Delete { get; }
public ReactiveCommand<Unit, Unit> Cancel { get; }
private async Task ExecuteImport()
{
if (!IsNew)
return;
string[]? result = await _windowService.CreateOpenFileDialog()
.HavingFilter(f => f.WithExtension("json").WithName("Artemis profile"))
.ShowAsync();
if (result == null)
return;
string json = await File.ReadAllTextAsync(result[0]);
ProfileConfigurationExportModel? profileConfigurationExportModel = null;
try
{
_profileCategory = profileCategory;
_windowService = windowService;
_profileService = profileService;
_profileEditorService = profileEditorService;
_profileConfiguration = profileConfiguration ?? profileService.CreateProfileConfiguration(profileCategory, "New profile", Enum.GetValues<MaterialIconKind>().First().ToString());
_profileName = _profileConfiguration.Name;
_iconType = _profileConfiguration.Icon.IconType;
_hotkeyMode = _profileConfiguration.HotkeyMode;
if (_profileConfiguration.EnableHotkey != null)
_enableHotkey = new Hotkey {Key = _profileConfiguration.EnableHotkey.Key, Modifiers = _profileConfiguration.EnableHotkey.Modifiers};
if (_profileConfiguration.DisableHotkey != null)
_disableHotkey = new Hotkey {Key = _profileConfiguration.DisableHotkey.Key, Modifiers = _profileConfiguration.DisableHotkey.Modifiers};
IsNew = profileConfiguration == null;
DisplayName = IsNew ? "Artemis | Add profile" : "Artemis | Edit profile";
Modules = new ObservableCollection<ProfileModuleViewModel?>(
pluginManagementService.GetFeaturesOfType<Module>().Where(m => !m.IsAlwaysAvailable).Select(m => new ProfileModuleViewModel(m))
);
Modules.Insert(0, null);
_selectedModule = Modules.FirstOrDefault(m => m?.Module == _profileConfiguration.Module);
VisualEditorViewModel = nodeVmFactory.NodeScriptViewModel(_profileConfiguration.ActivationCondition, true);
BrowseBitmapFile = ReactiveCommand.CreateFromTask(ExecuteBrowseBitmapFile);
OpenConditionEditor = ReactiveCommand.CreateFromTask(ExecuteOpenConditionEditor);
Confirm = ReactiveCommand.CreateFromTask(ExecuteConfirm);
Import = ReactiveCommand.CreateFromTask(ExecuteImport);
Delete = ReactiveCommand.CreateFromTask(ExecuteDelete);
Cancel = ReactiveCommand.Create(ExecuteCancel);
_moduleActivationRequirementsViewModel = this.WhenAnyValue(vm => vm.SelectedModule)
.Select(m => m != null ? new ModuleActivationRequirementsViewModel(m.Module) : null)
.ToProperty(this, vm => vm.ModuleActivationRequirementsViewModel);
Dispatcher.UIThread.Post(LoadIcon, DispatcherPriority.Background);
profileConfigurationExportModel = JsonConvert.DeserializeObject<ProfileConfigurationExportModel>(json, IProfileService.ExportSettings);
}
catch (JsonException e)
{
_windowService.ShowExceptionDialog("Import profile failed", e);
}
public bool IsNew { get; }
public ProfileConfiguration ProfileConfiguration
if (profileConfigurationExportModel == null)
{
get => _profileConfiguration;
set => RaiseAndSetIfChanged(ref _profileConfiguration, value);
await _windowService.ShowConfirmContentDialog("Import profile", "Failed to import this profile, make sure it is a valid Artemis profile.", "Confirm", null);
return;
}
public string ProfileName
try
{
get => _profileName;
set => RaiseAndSetIfChanged(ref _profileName, value);
ProfileConfiguration profileConfiguration = _profileService.ImportProfile(_profileCategory, profileConfigurationExportModel);
// Remove the temporary profile configuration
_profileService.RemoveProfileConfiguration(_profileConfiguration);
Close(profileConfiguration);
}
public ProfileConfigurationHotkeyMode HotkeyMode
catch (Exception e)
{
get => _hotkeyMode;
set => RaiseAndSetIfChanged(ref _hotkeyMode, value);
_windowService.ShowExceptionDialog("Import profile failed", e);
}
}
public Hotkey? EnableHotkey
{
get => _enableHotkey;
set => RaiseAndSetIfChanged(ref _enableHotkey, value);
}
private async Task ExecuteDelete()
{
if (IsNew)
return;
if (!await _windowService.ShowConfirmContentDialog("Delete profile", "Are you sure you want to permanently delete this profile?"))
return;
public Hotkey? DisableHotkey
{
get => _disableHotkey;
set => RaiseAndSetIfChanged(ref _disableHotkey, value);
}
if (_profileConfiguration.IsBeingEdited)
_profileEditorService.ChangeCurrentProfileConfiguration(null);
_profileService.RemoveProfileConfiguration(_profileConfiguration);
Close(_profileConfiguration);
}
public ObservableCollection<ProfileModuleViewModel?> Modules { get; }
private async Task ExecuteConfirm()
{
ProfileConfiguration.Name = ProfileName;
ProfileConfiguration.Module = SelectedModule?.Module;
ProfileConfiguration.HotkeyMode = HotkeyMode;
ProfileConfiguration.EnableHotkey = EnableHotkey;
ProfileConfiguration.DisableHotkey = DisableHotkey;
public ProfileModuleViewModel? SelectedModule
{
get => _selectedModule;
set => RaiseAndSetIfChanged(ref _selectedModule, value);
}
await SaveIcon();
public NodeScriptViewModel VisualEditorViewModel { get; }
public ModuleActivationRequirementsViewModel? ModuleActivationRequirementsViewModel => _moduleActivationRequirementsViewModel.Value;
_profileService.SaveProfileConfigurationIcon(ProfileConfiguration);
_profileService.SaveProfileCategory(_profileCategory);
Close(ProfileConfiguration);
}
public ReactiveCommand<Unit, Unit> OpenConditionEditor { get; }
public ReactiveCommand<Unit, Unit> BrowseBitmapFile { get; }
public ReactiveCommand<Unit, Unit> Confirm { get; }
public ReactiveCommand<Unit, Unit> Import { get; }
public ReactiveCommand<Unit, Unit> Delete { get; }
public ReactiveCommand<Unit, Unit> Cancel { get; }
private void ExecuteCancel()
{
if (IsNew)
_profileService.RemoveProfileConfiguration(_profileConfiguration);
Close(null);
}
private async Task ExecuteImport()
{
if (!IsNew)
return;
#region Icon
string[]? result = await _windowService.CreateOpenFileDialog()
.HavingFilter(f => f.WithExtension("json").WithName("Artemis profile"))
.ShowAsync();
public ProfileConfigurationIconType IconType
{
get => _iconType;
set => RaiseAndSetIfChanged(ref _iconType, value);
}
if (result == null)
return;
public ObservableCollection<ProfileIconViewModel>? MaterialIcons
{
get => _materialIcons;
set => RaiseAndSetIfChanged(ref _materialIcons, value);
}
string json = await File.ReadAllTextAsync(result[0]);
ProfileConfigurationExportModel? profileConfigurationExportModel = null;
public ProfileIconViewModel? SelectedMaterialIcon
{
get => _selectedMaterialIcon;
set => RaiseAndSetIfChanged(ref _selectedMaterialIcon, value);
}
public Bitmap? SelectedBitmapSource
{
get => _selectedBitmapSource;
set => RaiseAndSetIfChanged(ref _selectedBitmapSource, value);
}
private void LoadIcon()
{
// Preselect the icon based on streams if needed
if (_profileConfiguration.Icon.IconType == ProfileConfigurationIconType.BitmapImage)
try
{
profileConfigurationExportModel = JsonConvert.DeserializeObject<ProfileConfigurationExportModel>(json, IProfileService.ExportSettings);
}
catch (JsonException e)
{
_windowService.ShowExceptionDialog("Import profile failed", e);
}
if (profileConfigurationExportModel == null)
{
await _windowService.ShowConfirmContentDialog("Import profile", "Failed to import this profile, make sure it is a valid Artemis profile.", "Confirm", null);
return;
}
try
{
ProfileConfiguration profileConfiguration = _profileService.ImportProfile(_profileCategory, profileConfigurationExportModel);
// Remove the temporary profile configuration
_profileService.RemoveProfileConfiguration(_profileConfiguration);
Close(profileConfiguration);
SelectedBitmapSource = new Bitmap(_profileConfiguration.Icon.GetIconStream());
}
catch (Exception e)
{
_windowService.ShowExceptionDialog("Import profile failed", e);
_windowService.ShowConfirmContentDialog("Failed to load profile icon", e.Message, "Meh", null);
}
}
private async Task ExecuteDelete()
{
if (IsNew)
return;
if (!await _windowService.ShowConfirmContentDialog("Delete profile", "Are you sure you want to permanently delete this profile?"))
return;
// Prepare the contents of the dropdown box, it should be virtualized so no need to wait with this
ObservableCollection<ProfileIconViewModel> icons = new(Enum.GetValues<MaterialIconKind>()
.Select(kind => new ProfileIconViewModel(kind))
.DistinctBy(vm => vm.DisplayName)
.OrderBy(vm => vm.DisplayName));
if (_profileConfiguration.IsBeingEdited)
_profileEditorService.ChangeCurrentProfileConfiguration(null);
_profileService.RemoveProfileConfiguration(_profileConfiguration);
Close(_profileConfiguration);
}
private async Task ExecuteConfirm()
{
ProfileConfiguration.Name = ProfileName;
ProfileConfiguration.Module = SelectedModule?.Module;
ProfileConfiguration.HotkeyMode = HotkeyMode;
ProfileConfiguration.EnableHotkey = EnableHotkey;
ProfileConfiguration.DisableHotkey = DisableHotkey;
await SaveIcon();
_profileService.SaveProfileConfigurationIcon(ProfileConfiguration);
_profileService.SaveProfileCategory(_profileCategory);
Close(ProfileConfiguration);
}
private void ExecuteCancel()
{
if (IsNew)
_profileService.RemoveProfileConfiguration(_profileConfiguration);
Close(null);
}
#region Icon
public ProfileConfigurationIconType IconType
{
get => _iconType;
set => RaiseAndSetIfChanged(ref _iconType, value);
}
public ObservableCollection<ProfileIconViewModel>? MaterialIcons
{
get => _materialIcons;
set => RaiseAndSetIfChanged(ref _materialIcons, value);
}
public ProfileIconViewModel? SelectedMaterialIcon
{
get => _selectedMaterialIcon;
set => RaiseAndSetIfChanged(ref _selectedMaterialIcon, value);
}
public Bitmap? SelectedBitmapSource
{
get => _selectedBitmapSource;
set => RaiseAndSetIfChanged(ref _selectedBitmapSource, value);
}
private void LoadIcon()
{
// Preselect the icon based on streams if needed
if (_profileConfiguration.Icon.IconType == ProfileConfigurationIconType.BitmapImage)
{
try
{
SelectedBitmapSource = new Bitmap(_profileConfiguration.Icon.GetIconStream());
}
catch (Exception e)
{
_windowService.ShowConfirmContentDialog("Failed to load profile icon", e.Message, "Meh", null);
}
}
// Prepare the contents of the dropdown box, it should be virtualized so no need to wait with this
ObservableCollection<ProfileIconViewModel> icons = new(Enum.GetValues<MaterialIconKind>()
.Select(kind => new ProfileIconViewModel(kind))
.DistinctBy(vm => vm.DisplayName)
.OrderBy(vm => vm.DisplayName));
// Preselect the icon or fall back to a random one
SelectedMaterialIcon = !IsNew && Enum.TryParse(_profileConfiguration.Icon.IconName, out MaterialIconKind enumValue)
? icons.FirstOrDefault(m => m.Icon == enumValue)
: icons.ElementAt(new Random().Next(0, icons.Count - 1));
MaterialIcons = icons;
}
private async Task SaveIcon()
{
if (IconType == ProfileConfigurationIconType.MaterialIcon && SelectedMaterialIcon != null)
{
ProfileConfiguration.Icon.SetIconByName(SelectedMaterialIcon.Icon.ToString());
}
else if (_selectedIconPath != null)
{
await using FileStream fileStream = File.OpenRead(_selectedIconPath);
ProfileConfiguration.Icon.SetIconByStream(Path.GetFileName(_selectedIconPath), fileStream);
}
}
private async Task ExecuteBrowseBitmapFile()
{
string[]? result = await _windowService.CreateOpenFileDialog()
.HavingFilter(f => f.WithExtension("png").WithExtension("jpg").WithExtension("bmp").WithName("Bitmap image"))
.ShowAsync();
if (result == null)
return;
SelectedBitmapSource = new Bitmap(result[0]);
_selectedIconPath = result[0];
}
private async Task ExecuteOpenConditionEditor()
{
await _windowService.ShowDialogAsync<NodeScriptWindowViewModel, bool>(("nodeScript", ProfileConfiguration.ActivationCondition));
}
#endregion
// Preselect the icon or fall back to a random one
SelectedMaterialIcon = !IsNew && Enum.TryParse(_profileConfiguration.Icon.IconName, out MaterialIconKind enumValue)
? icons.FirstOrDefault(m => m.Icon == enumValue)
: icons.ElementAt(new Random().Next(0, icons.Count - 1));
MaterialIcons = icons;
}
private async Task SaveIcon()
{
if (IconType == ProfileConfigurationIconType.MaterialIcon && SelectedMaterialIcon != null)
{
ProfileConfiguration.Icon.SetIconByName(SelectedMaterialIcon.Icon.ToString());
}
else if (_selectedIconPath != null)
{
await using FileStream fileStream = File.OpenRead(_selectedIconPath);
ProfileConfiguration.Icon.SetIconByStream(Path.GetFileName(_selectedIconPath), fileStream);
}
}
private async Task ExecuteBrowseBitmapFile()
{
string[]? result = await _windowService.CreateOpenFileDialog()
.HavingFilter(f => f.WithExtension("png").WithExtension("jpg").WithExtension("bmp").WithName("Bitmap image"))
.ShowAsync();
if (result == null)
return;
SelectedBitmapSource = new Bitmap(result[0]);
_selectedIconPath = result[0];
}
private async Task ExecuteOpenConditionEditor()
{
await _windowService.ShowDialogAsync<NodeScriptWindowViewModel, bool>(("nodeScript", ProfileConfiguration.ActivationCondition));
}
#endregion
}
@@ -1,16 +1,15 @@
using Artemis.UI.Shared;
using Material.Icons;
namespace Artemis.UI.Screens.Sidebar
{
public class ProfileIconViewModel : ViewModelBase
{
public ProfileIconViewModel(MaterialIconKind icon)
{
Icon = icon;
DisplayName = icon.ToString();
}
namespace Artemis.UI.Screens.Sidebar;
public MaterialIconKind Icon { get; }
public class ProfileIconViewModel : ViewModelBase
{
public ProfileIconViewModel(MaterialIconKind icon)
{
Icon = icon;
DisplayName = icon.ToString();
}
public MaterialIconKind Icon { get; }
}
@@ -2,22 +2,21 @@
using Artemis.UI.Shared;
using Material.Icons;
namespace Artemis.UI.Screens.Sidebar
namespace Artemis.UI.Screens.Sidebar;
public class ProfileModuleViewModel : ViewModelBase
{
public class ProfileModuleViewModel : ViewModelBase
public ProfileModuleViewModel(Module module)
{
public ProfileModuleViewModel(Module module)
{
Module = module;
Name = module.Info.Name;
Icon = module.Info.ResolvedIcon ?? MaterialIconKind.QuestionMark.ToString();
Description = module.Info.Description;
}
public string Icon { get; }
public string Name { get; }
public string? Description { get; }
public Module Module { get; }
Module = module;
Name = module.Info.Name;
Icon = module.Info.ResolvedIcon ?? MaterialIconKind.QuestionMark.ToString();
Description = module.Info.Description;
}
public string Icon { get; }
public string Name { get; }
public string? Description { get; }
public Module Module { get; }
}
@@ -12,128 +12,126 @@ using Avalonia.Media.Imaging;
using Avalonia.ReactiveUI;
using Avalonia.VisualTree;
namespace Artemis.UI.Screens.Sidebar
namespace Artemis.UI.Screens.Sidebar;
public class SidebarCategoryView : ReactiveUserControl<SidebarCategoryViewModel>
{
public class SidebarCategoryView : ReactiveUserControl<SidebarCategoryViewModel>
private static Image? _dragAdorner;
private Point _dragStartPosition;
private Point _elementDragOffset;
private ListBox _listBox;
public SidebarCategoryView()
{
private static Image? _dragAdorner;
private Point _dragStartPosition;
private Point _elementDragOffset;
private ListBox _listBox;
InitializeComponent();
_listBox = this.Get<ListBox>("SidebarListBox");
public SidebarCategoryView()
{
InitializeComponent();
_listBox = this.Get<ListBox>("SidebarListBox");
AddHandler(DragDrop.DragEnterEvent, HandleDragEnterEvent, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true);
AddHandler(DragDrop.DragOverEvent, HandleDragOver, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true);
AddHandler(PointerEnterEvent, HandlePointerEnter, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true);
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void InputElement_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
{
if (e.InitialPressMouseButton == MouseButton.Left)
ViewModel?.ToggleCollapsed.Execute().Subscribe();
}
#region Dragging
private void HandlePointerEnter(object? sender, PointerEventArgs e)
{
DisposeDragAdorner();
}
private void HandleDragEnterEvent(object? sender, DragEventArgs e)
{
CreateDragAdorner(e);
}
private void CreateDragAdorner(DragEventArgs e)
{
if (_dragAdorner != null)
return;
if (e.Source is not Control c)
return;
// Get the list box item that raised the event
ListBoxItem? container = c.FindLogicalAncestorOfType<ListBoxItem>();
if (container == null)
return;
// Take a snapshot of said tree view item and add it as an adorner
ITransform? originalTransform = container.RenderTransform;
try
{
_dragStartPosition = e.GetPosition(this.FindAncestorOfType<Window>());
_elementDragOffset = e.GetPosition(container);
RenderTargetBitmap renderTarget = new(new PixelSize((int) container.Bounds.Width, (int) container.Bounds.Height));
container.RenderTransform = new TranslateTransform(container.Bounds.X * -1, container.Bounds.Y * -1);
renderTarget.Render(container);
_dragAdorner = new Image
{
Source = renderTarget,
VerticalAlignment = VerticalAlignment.Top,
HorizontalAlignment = HorizontalAlignment.Left,
Stretch = Stretch.None,
IsHitTestVisible = false
};
AdornerLayer.GetAdornerLayer(this)!.Children.Add(_dragAdorner);
}
finally
{
container.RenderTransform = originalTransform;
}
}
private void HandleDragOver(object? sender, DragEventArgs e)
{
UpdateDragAdorner(e.GetPosition(this.FindAncestorOfType<Window>()));
}
private void HandleLeaveEvent(object? sender, RoutedEventArgs e)
{
// If there is currently an adorner, dispose of it
DisposeDragAdorner();
}
private void HandleDrop(object? sender, DragEventArgs e)
{
// If there is currently an adorner, dispose of it
DisposeDragAdorner();
}
private void DisposeDragAdorner()
{
if (_dragAdorner == null)
return;
AdornerLayer.GetAdornerLayer(this)!.Children.Remove(_dragAdorner);
(_dragAdorner.Source as RenderTargetBitmap)?.Dispose();
_dragAdorner = null;
}
private void UpdateDragAdorner(Point position)
{
if (_dragAdorner == null)
return;
_dragAdorner.RenderTransform = new TranslateTransform(_dragStartPosition.X - _elementDragOffset.X, position.Y - _elementDragOffset.Y);
}
private void InputElement_OnPointerMoved(object? sender, PointerEventArgs e)
{
DisposeDragAdorner();
}
#endregion
AddHandler(DragDrop.DragEnterEvent, HandleDragEnterEvent, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true);
AddHandler(DragDrop.DragOverEvent, HandleDragOver, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true);
AddHandler(PointerEnterEvent, HandlePointerEnter, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true);
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void InputElement_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
{
if (e.InitialPressMouseButton == MouseButton.Left)
ViewModel?.ToggleCollapsed.Execute().Subscribe();
}
#region Dragging
private void HandlePointerEnter(object? sender, PointerEventArgs e)
{
DisposeDragAdorner();
}
private void HandleDragEnterEvent(object? sender, DragEventArgs e)
{
CreateDragAdorner(e);
}
private void CreateDragAdorner(DragEventArgs e)
{
if (_dragAdorner != null)
return;
if (e.Source is not Control c)
return;
// Get the list box item that raised the event
ListBoxItem? container = c.FindLogicalAncestorOfType<ListBoxItem>();
if (container == null)
return;
// Take a snapshot of said tree view item and add it as an adorner
ITransform? originalTransform = container.RenderTransform;
try
{
_dragStartPosition = e.GetPosition(this.FindAncestorOfType<Window>());
_elementDragOffset = e.GetPosition(container);
RenderTargetBitmap renderTarget = new(new PixelSize((int) container.Bounds.Width, (int) container.Bounds.Height));
container.RenderTransform = new TranslateTransform(container.Bounds.X * -1, container.Bounds.Y * -1);
renderTarget.Render(container);
_dragAdorner = new Image
{
Source = renderTarget,
VerticalAlignment = VerticalAlignment.Top,
HorizontalAlignment = HorizontalAlignment.Left,
Stretch = Stretch.None,
IsHitTestVisible = false
};
AdornerLayer.GetAdornerLayer(this)!.Children.Add(_dragAdorner);
}
finally
{
container.RenderTransform = originalTransform;
}
}
private void HandleDragOver(object? sender, DragEventArgs e)
{
UpdateDragAdorner(e.GetPosition(this.FindAncestorOfType<Window>()));
}
private void HandleLeaveEvent(object? sender, RoutedEventArgs e)
{
// If there is currently an adorner, dispose of it
DisposeDragAdorner();
}
private void HandleDrop(object? sender, DragEventArgs e)
{
// If there is currently an adorner, dispose of it
DisposeDragAdorner();
}
private void DisposeDragAdorner()
{
if (_dragAdorner == null)
return;
AdornerLayer.GetAdornerLayer(this)!.Children.Remove(_dragAdorner);
(_dragAdorner.Source as RenderTargetBitmap)?.Dispose();
_dragAdorner = null;
}
private void UpdateDragAdorner(Point position)
{
if (_dragAdorner == null)
return;
_dragAdorner.RenderTransform = new TranslateTransform(_dragStartPosition.X - _elementDragOffset.X, position.Y - _elementDragOffset.Y);
}
private void InputElement_OnPointerMoved(object? sender, PointerEventArgs e)
{
DisposeDragAdorner();
}
#endregion
}
@@ -7,7 +7,6 @@ using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Events;
using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared;
@@ -18,170 +17,169 @@ using DynamicData;
using DynamicData.Binding;
using ReactiveUI;
namespace Artemis.UI.Screens.Sidebar
namespace Artemis.UI.Screens.Sidebar;
public class SidebarCategoryViewModel : ActivatableViewModelBase
{
public class SidebarCategoryViewModel : ActivatableViewModelBase
private readonly IProfileService _profileService;
private readonly SidebarViewModel _sidebarViewModel;
private readonly ISidebarVmFactory _vmFactory;
private readonly IWindowService _windowService;
private ObservableAsPropertyHelper<bool>? _isCollapsed;
private ObservableAsPropertyHelper<bool>? _isSuspended;
private SidebarProfileConfigurationViewModel? _selectedProfileConfiguration;
public SidebarCategoryViewModel(SidebarViewModel sidebarViewModel, ProfileCategory profileCategory, IProfileService profileService, IWindowService windowService,
IProfileEditorService profileEditorService, ISidebarVmFactory vmFactory)
{
private readonly IProfileService _profileService;
private readonly SidebarViewModel _sidebarViewModel;
private readonly ISidebarVmFactory _vmFactory;
private readonly IWindowService _windowService;
private SidebarProfileConfigurationViewModel? _selectedProfileConfiguration;
private ObservableAsPropertyHelper<bool>? _isCollapsed;
private ObservableAsPropertyHelper<bool>? _isSuspended;
_sidebarViewModel = sidebarViewModel;
_profileService = profileService;
_windowService = windowService;
_vmFactory = vmFactory;
public SidebarCategoryViewModel(SidebarViewModel sidebarViewModel, ProfileCategory profileCategory, IProfileService profileService, IWindowService windowService,
IProfileEditorService profileEditorService, ISidebarVmFactory vmFactory)
ProfileCategory = profileCategory;
SourceList<ProfileConfiguration> profileConfigurations = new();
// Only show items when not collapsed
IObservable<Func<ProfileConfiguration, bool>> profileConfigurationsFilter = this.WhenAnyValue(vm => vm.IsCollapsed).Select(b => new Func<object, bool>(_ => !b));
profileConfigurations.Connect()
.Filter(profileConfigurationsFilter)
.Sort(SortExpressionComparer<ProfileConfiguration>.Ascending(c => c.Order))
.Transform(c => _vmFactory.SidebarProfileConfigurationViewModel(_sidebarViewModel, c))
.Bind(out ReadOnlyObservableCollection<SidebarProfileConfigurationViewModel> profileConfigurationViewModels)
.Subscribe();
ProfileConfigurations = profileConfigurationViewModels;
ToggleCollapsed = ReactiveCommand.Create(ExecuteToggleCollapsed);
ToggleSuspended = ReactiveCommand.Create(ExecuteToggleSuspended);
AddProfile = ReactiveCommand.CreateFromTask(ExecuteAddProfile);
EditCategory = ReactiveCommand.CreateFromTask(ExecuteEditCategory);
MoveUp = ReactiveCommand.Create(ExecuteMoveUp);
MoveDown = ReactiveCommand.Create(ExecuteMoveDown);
this.WhenActivated(d =>
{
_sidebarViewModel = sidebarViewModel;
_profileService = profileService;
_windowService = windowService;
_vmFactory = vmFactory;
// Update the list of profiles whenever the category fires events
Observable.FromEventPattern<ProfileConfigurationEventArgs>(x => profileCategory.ProfileConfigurationAdded += x, x => profileCategory.ProfileConfigurationAdded -= x)
.Subscribe(e => profileConfigurations.Add(e.EventArgs.ProfileConfiguration))
.DisposeWith(d);
Observable.FromEventPattern<ProfileConfigurationEventArgs>(x => profileCategory.ProfileConfigurationRemoved += x, x => profileCategory.ProfileConfigurationRemoved -= x)
.Subscribe(e => profileConfigurations.Remove(e.EventArgs.ProfileConfiguration))
.DisposeWith(d);
ProfileCategory = profileCategory;
SourceList<ProfileConfiguration> profileConfigurations = new();
profileEditorService.ProfileConfiguration.Subscribe(p => SelectedProfileConfiguration = ProfileConfigurations.FirstOrDefault(c => ReferenceEquals(c.ProfileConfiguration, p)))
.DisposeWith(d);
// Only show items when not collapsed
IObservable<Func<ProfileConfiguration, bool>> profileConfigurationsFilter = this.WhenAnyValue(vm => vm.IsCollapsed).Select(b => new Func<object, bool>(_ => !b));
profileConfigurations.Connect()
.Filter(profileConfigurationsFilter)
.Sort(SortExpressionComparer<ProfileConfiguration>.Ascending(c => c.Order))
.Transform(c => _vmFactory.SidebarProfileConfigurationViewModel(_sidebarViewModel, c))
.Bind(out ReadOnlyObservableCollection<SidebarProfileConfigurationViewModel> profileConfigurationViewModels)
.Subscribe();
ProfileConfigurations = profileConfigurationViewModels;
_isCollapsed = ProfileCategory.WhenAnyValue(vm => vm.IsCollapsed).ToProperty(this, vm => vm.IsCollapsed).DisposeWith(d);
_isSuspended = ProfileCategory.WhenAnyValue(vm => vm.IsSuspended).ToProperty(this, vm => vm.IsSuspended).DisposeWith(d);
ToggleCollapsed = ReactiveCommand.Create(ExecuteToggleCollapsed);
ToggleSuspended = ReactiveCommand.Create(ExecuteToggleSuspended);
AddProfile = ReactiveCommand.CreateFromTask(ExecuteAddProfile);
EditCategory = ReactiveCommand.CreateFromTask(ExecuteEditCategory);
MoveUp = ReactiveCommand.Create(ExecuteMoveUp);
MoveDown = ReactiveCommand.Create(ExecuteMoveDown);
// Change the current profile configuration when a new one is selected
this.WhenAnyValue(vm => vm.SelectedProfileConfiguration).WhereNotNull().Subscribe(s => profileEditorService.ChangeCurrentProfileConfiguration(s.ProfileConfiguration));
});
this.WhenActivated(d =>
{
// Update the list of profiles whenever the category fires events
Observable.FromEventPattern<ProfileConfigurationEventArgs>(x => profileCategory.ProfileConfigurationAdded += x, x => profileCategory.ProfileConfigurationAdded -= x)
.Subscribe(e => profileConfigurations.Add(e.EventArgs.ProfileConfiguration))
.DisposeWith(d);
Observable.FromEventPattern<ProfileConfigurationEventArgs>(x => profileCategory.ProfileConfigurationRemoved += x, x => profileCategory.ProfileConfigurationRemoved -= x)
.Subscribe(e => profileConfigurations.Remove(e.EventArgs.ProfileConfiguration))
.DisposeWith(d);
profileEditorService.ProfileConfiguration.Subscribe(p => SelectedProfileConfiguration = ProfileConfigurations.FirstOrDefault(c => ReferenceEquals(c.ProfileConfiguration, p)))
.DisposeWith(d);
_isCollapsed = ProfileCategory.WhenAnyValue(vm => vm.IsCollapsed).ToProperty(this, vm => vm.IsCollapsed).DisposeWith(d);
_isSuspended = ProfileCategory.WhenAnyValue(vm => vm.IsSuspended).ToProperty(this, vm => vm.IsSuspended).DisposeWith(d);
// Change the current profile configuration when a new one is selected
this.WhenAnyValue(vm => vm.SelectedProfileConfiguration).WhereNotNull().Subscribe(s => profileEditorService.ChangeCurrentProfileConfiguration(s.ProfileConfiguration));
});
profileConfigurations.Edit(updater =>
{
foreach (ProfileConfiguration profileConfiguration in profileCategory.ProfileConfigurations)
updater.Add(profileConfiguration);
});
}
public ReactiveCommand<Unit, Unit> ToggleCollapsed { get; }
public ReactiveCommand<Unit, Unit> ToggleSuspended { get; }
public ReactiveCommand<Unit, Unit> AddProfile { get; }
public ReactiveCommand<Unit, Unit> EditCategory { get; }
public ReactiveCommand<Unit, Unit> MoveUp { get; }
public ReactiveCommand<Unit, Unit> MoveDown { get; }
public ProfileCategory ProfileCategory { get; }
public ReadOnlyObservableCollection<SidebarProfileConfigurationViewModel> ProfileConfigurations { get; }
public bool IsCollapsed => _isCollapsed?.Value ?? false;
public bool IsSuspended => _isSuspended?.Value ?? false;
public SidebarProfileConfigurationViewModel? SelectedProfileConfiguration
profileConfigurations.Edit(updater =>
{
get => _selectedProfileConfiguration;
set => RaiseAndSetIfChanged(ref _selectedProfileConfiguration, value);
}
foreach (ProfileConfiguration profileConfiguration in profileCategory.ProfileConfigurations)
updater.Add(profileConfiguration);
});
}
private async Task ExecuteEditCategory()
public ReactiveCommand<Unit, Unit> ToggleCollapsed { get; }
public ReactiveCommand<Unit, Unit> ToggleSuspended { get; }
public ReactiveCommand<Unit, Unit> AddProfile { get; }
public ReactiveCommand<Unit, Unit> EditCategory { get; }
public ReactiveCommand<Unit, Unit> MoveUp { get; }
public ReactiveCommand<Unit, Unit> MoveDown { get; }
public ProfileCategory ProfileCategory { get; }
public ReadOnlyObservableCollection<SidebarProfileConfigurationViewModel> ProfileConfigurations { get; }
public bool IsCollapsed => _isCollapsed?.Value ?? false;
public bool IsSuspended => _isSuspended?.Value ?? false;
public SidebarProfileConfigurationViewModel? SelectedProfileConfiguration
{
get => _selectedProfileConfiguration;
set => RaiseAndSetIfChanged(ref _selectedProfileConfiguration, value);
}
public void AddProfileConfiguration(ProfileConfiguration profileConfiguration, int? index)
{
ProfileCategory oldCategory = profileConfiguration.Category;
ProfileCategory.AddProfileConfiguration(profileConfiguration, index);
_profileService.SaveProfileCategory(ProfileCategory);
// If the profile moved to a new category, also save the old category
if (oldCategory != ProfileCategory)
_profileService.SaveProfileCategory(oldCategory);
}
private async Task ExecuteEditCategory()
{
await _windowService.CreateContentDialog()
.WithTitle("Edit category")
.WithViewModel(out SidebarCategoryEditViewModel vm, ("category", ProfileCategory))
.HavingPrimaryButton(b => b.WithText("Confirm").WithCommand(vm.Confirm))
.HavingSecondaryButton(b => b.WithText("Delete").WithCommand(vm.Delete))
.WithCloseButtonText("Cancel")
.WithDefaultButton(ContentDialogButton.Primary)
.ShowAsync();
_sidebarViewModel.UpdateProfileCategories();
}
private async Task ExecuteAddProfile()
{
ProfileConfiguration? result = await _windowService.ShowDialogAsync<ProfileConfigurationEditViewModel, ProfileConfiguration?>(
("profileCategory", ProfileCategory),
("profileConfiguration", null)
);
if (result != null)
{
await _windowService.CreateContentDialog()
.WithTitle("Edit category")
.WithViewModel(out SidebarCategoryEditViewModel vm, ("category", ProfileCategory))
.HavingPrimaryButton(b => b.WithText("Confirm").WithCommand(vm.Confirm))
.HavingSecondaryButton(b => b.WithText("Delete").WithCommand(vm.Delete))
.WithCloseButtonText("Cancel")
.WithDefaultButton(ContentDialogButton.Primary)
.ShowAsync();
_sidebarViewModel.UpdateProfileCategories();
}
private async Task ExecuteAddProfile()
{
ProfileConfiguration? result = await _windowService.ShowDialogAsync<ProfileConfigurationEditViewModel, ProfileConfiguration?>(
("profileCategory", ProfileCategory),
("profileConfiguration", null)
);
if (result != null)
{
SidebarProfileConfigurationViewModel viewModel = _vmFactory.SidebarProfileConfigurationViewModel(_sidebarViewModel, result);
SelectedProfileConfiguration = viewModel;
}
}
private void ExecuteToggleCollapsed()
{
ProfileCategory.IsCollapsed = !ProfileCategory.IsCollapsed;
_profileService.SaveProfileCategory(ProfileCategory);
}
private void ExecuteToggleSuspended()
{
ProfileCategory.IsSuspended = !ProfileCategory.IsSuspended;
_profileService.SaveProfileCategory(ProfileCategory);
}
private void ExecuteMoveUp()
{
List<ProfileCategory> categories = _profileService.ProfileCategories.OrderBy(p => p.Order).ToList();
int index = categories.IndexOf(ProfileCategory);
if (index <= 0)
return;
categories[index - 1].Order++;
ProfileCategory.Order--;
_profileService.SaveProfileCategory(categories[index - 1]);
_profileService.SaveProfileCategory(ProfileCategory);
_sidebarViewModel.UpdateProfileCategories();
}
private void ExecuteMoveDown()
{
List<ProfileCategory> categories = _profileService.ProfileCategories.OrderBy(p => p.Order).ToList();
int index = categories.IndexOf(ProfileCategory);
if (index >= categories.Count - 1)
return;
categories[index + 1].Order--;
ProfileCategory.Order++;
_profileService.SaveProfileCategory(categories[index + 1]);
_profileService.SaveProfileCategory(ProfileCategory);
_sidebarViewModel.UpdateProfileCategories();
}
public void AddProfileConfiguration(ProfileConfiguration profileConfiguration, int? index)
{
ProfileCategory oldCategory = profileConfiguration.Category;
ProfileCategory.AddProfileConfiguration(profileConfiguration, index);
_profileService.SaveProfileCategory(ProfileCategory);
// If the profile moved to a new category, also save the old category
if (oldCategory != ProfileCategory)
_profileService.SaveProfileCategory(oldCategory);
SidebarProfileConfigurationViewModel viewModel = _vmFactory.SidebarProfileConfigurationViewModel(_sidebarViewModel, result);
SelectedProfileConfiguration = viewModel;
}
}
private void ExecuteToggleCollapsed()
{
ProfileCategory.IsCollapsed = !ProfileCategory.IsCollapsed;
_profileService.SaveProfileCategory(ProfileCategory);
}
private void ExecuteToggleSuspended()
{
ProfileCategory.IsSuspended = !ProfileCategory.IsSuspended;
_profileService.SaveProfileCategory(ProfileCategory);
}
private void ExecuteMoveUp()
{
List<ProfileCategory> categories = _profileService.ProfileCategories.OrderBy(p => p.Order).ToList();
int index = categories.IndexOf(ProfileCategory);
if (index <= 0)
return;
categories[index - 1].Order++;
ProfileCategory.Order--;
_profileService.SaveProfileCategory(categories[index - 1]);
_profileService.SaveProfileCategory(ProfileCategory);
_sidebarViewModel.UpdateProfileCategories();
}
private void ExecuteMoveDown()
{
List<ProfileCategory> categories = _profileService.ProfileCategories.OrderBy(p => p.Order).ToList();
int index = categories.IndexOf(ProfileCategory);
if (index >= categories.Count - 1)
return;
categories[index + 1].Order--;
ProfileCategory.Order++;
_profileService.SaveProfileCategory(categories[index + 1]);
_profileService.SaveProfileCategory(ProfileCategory);
_sidebarViewModel.UpdateProfileCategories();
}
}
Loaded 100 of 691 files, more files were not shown because too many files have changed in this diff. Show more