mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-12 11:56:30 +00:00
Code style - Use file scoped namespaces
Code style - Ran code cleanup
This commit is contained in:
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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
+2
-4
@@ -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()
|
||||
{
|
||||
|
||||
+113
-114
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-4
@@ -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()
|
||||
{
|
||||
|
||||
+116
-117
@@ -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));
|
||||
|
||||
@@ -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()
|
||||
|
||||
+2
-3
@@ -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()
|
||||
{
|
||||
|
||||
+2
-3
@@ -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()
|
||||
{
|
||||
|
||||
-1
@@ -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"
|
||||
|
||||
+2
-3
@@ -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
-3
@@ -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()
|
||||
{
|
||||
|
||||
+1
-1
@@ -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
-3
@@ -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
-3
@@ -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()
|
||||
{
|
||||
|
||||
+1
-7
@@ -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)
|
||||
{
|
||||
|
||||
-1
@@ -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">
|
||||
|
||||
+7
-6
@@ -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;
|
||||
|
||||
+35
-41
@@ -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);
|
||||
|
||||
+1
-1
@@ -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
-2
@@ -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
-6
@@ -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))
|
||||
|
||||
+2
-2
@@ -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())
|
||||
{
|
||||
|
||||
+1
-1
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in new issue
Block a user