diff --git a/src/Artemis.Core/Services/RgbService.cs b/src/Artemis.Core/Services/RgbService.cs index 2e1ea8c99..d8af2526b 100644 --- a/src/Artemis.Core/Services/RgbService.cs +++ b/src/Artemis.Core/Services/RgbService.cs @@ -43,7 +43,7 @@ namespace Artemis.Core.Services _deviceRepository = deviceRepository; _targetFrameRateSetting = settingsService.GetSetting("Core.TargetFrameRate", 30); _renderScaleSetting = settingsService.GetSetting("Core.RenderScale", 0.25); - _preferredGraphicsContext = _settingsService.GetSetting("Core.PreferredGraphicsContext", "Vulkan"); + _preferredGraphicsContext = _settingsService.GetSetting("Core.PreferredGraphicsContext", "Software"); Surface = new RGBSurface(); Utilities.RenderScaleMultiplier = (int) (1 / _renderScaleSetting.Value); diff --git a/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabViewModel.cs index 8b4660b89..e4bf4e961 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabViewModel.cs @@ -133,7 +133,7 @@ namespace Artemis.UI.Screens.Settings public PluginSetting UIColorScheme => _settingsService.GetSetting("UI.ColorScheme", ApplicationColorScheme.Automatic); public PluginSetting ProfileEditorShowDataModelValues => _settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false); public PluginSetting CoreLoggingLevel => _settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Information); - public PluginSetting CorePreferredGraphicsContext => _settingsService.GetSetting("Core.PreferredGraphicsContext", "Vulkan"); + public PluginSetting CorePreferredGraphicsContext => _settingsService.GetSetting("Core.PreferredGraphicsContext", "Software"); public PluginSetting CoreRenderScale => _settingsService.GetSetting("Core.RenderScale", 0.25); public PluginSetting CoreTargetFrameRate => _settingsService.GetSetting("Core.TargetFrameRate", 30); public PluginSetting WebServerPort => _settingsService.GetSetting("WebServer.Port", 9696); diff --git a/src/Artemis.UI/Screens/SurfaceEditor/ListDeviceView.axaml b/src/Artemis.UI/Screens/SurfaceEditor/ListDeviceView.axaml index fda598852..b34739f21 100644 --- a/src/Artemis.UI/Screens/SurfaceEditor/ListDeviceView.axaml +++ b/src/Artemis.UI/Screens/SurfaceEditor/ListDeviceView.axaml @@ -2,7 +2,16 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" + xmlns:surfaceEditor="clr-namespace:Artemis.UI.Screens.SurfaceEditor" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Artemis.UI.Screens.SurfaceEditor.ListDeviceView"> - Welcome to Avalonia! + x:Class="Artemis.UI.Screens.SurfaceEditor.ListDeviceView" + x:DataType="surfaceEditor:ListDeviceViewModel"> + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/SurfaceEditor/ListDeviceViewModel.cs b/src/Artemis.UI/Screens/SurfaceEditor/ListDeviceViewModel.cs index ee9ded844..fad0089f6 100644 --- a/src/Artemis.UI/Screens/SurfaceEditor/ListDeviceViewModel.cs +++ b/src/Artemis.UI/Screens/SurfaceEditor/ListDeviceViewModel.cs @@ -1,4 +1,5 @@ -using Artemis.Core; +using System; +using Artemis.Core; using Artemis.UI.Shared; using SkiaSharp; @@ -6,6 +7,8 @@ namespace Artemis.UI.Screens.SurfaceEditor; public class ListDeviceViewModel : ViewModelBase { + private static readonly Random Random = new(); + private SKColor _color; private bool _isSelected; @@ -13,6 +16,7 @@ public class ListDeviceViewModel : ViewModelBase { Device = device; SurfaceEditorViewModel = surfaceEditorViewModel; + Color = SKColor.FromHsv(Random.NextSingle() * 360, 95, 100); } public ArtemisDevice Device { get; } diff --git a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceDeviceViewModel.cs b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceDeviceViewModel.cs index f1cc051c5..331054522 100644 --- a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceDeviceViewModel.cs +++ b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceDeviceViewModel.cs @@ -1,14 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reactive; -using System.Threading.Tasks; using Artemis.Core; using Artemis.Core.Services; -using Artemis.UI.Ninject.Factories; using Artemis.UI.Shared; -using Artemis.UI.Shared.Services; -using ReactiveUI; using RGB.NET.Core; using SkiaSharp; using Point = Avalonia.Point; @@ -17,35 +12,21 @@ namespace Artemis.UI.Screens.SurfaceEditor; public class SurfaceDeviceViewModel : ActivatableViewModelBase { - private readonly IDeviceService _deviceService; - private readonly IDeviceVmFactory _deviceVmFactory; private readonly IRgbService _rgbService; private readonly ISettingsService _settingsService; - private readonly IWindowService _windowService; private double _dragOffsetX; private double _dragOffsetY; private bool _isSelected; - public SurfaceDeviceViewModel(ArtemisDevice device, SurfaceEditorViewModel surfaceEditorViewModel, IRgbService rgbService, IDeviceService deviceService, ISettingsService settingsService, - IDeviceVmFactory deviceVmFactory, - IWindowService windowService) + public SurfaceDeviceViewModel(ArtemisDevice device, SurfaceEditorViewModel surfaceEditorViewModel, IRgbService rgbService, ISettingsService settingsService) { _rgbService = rgbService; - _deviceService = deviceService; _settingsService = settingsService; - _deviceVmFactory = deviceVmFactory; - _windowService = windowService; Device = device; SurfaceEditorViewModel = surfaceEditorViewModel; - - IdentifyDevice = ReactiveCommand.Create(ExecuteIdentifyDevice); - ViewProperties = ReactiveCommand.CreateFromTask(ExecuteViewProperties); } - public ReactiveCommand IdentifyDevice { get; } - public ReactiveCommand ViewProperties { get; } - public ArtemisDevice Device { get; } public SurfaceEditorViewModel SurfaceEditorViewModel { get; } @@ -96,16 +77,6 @@ public class SurfaceDeviceViewModel : ActivatableViewModelBase } } - private void ExecuteIdentifyDevice(ArtemisDevice device) - { - _deviceService.IdentifyDevice(device); - } - - private async Task ExecuteViewProperties(ArtemisDevice device) - { - await _windowService.ShowDialogAsync(_deviceVmFactory.DevicePropertiesViewModel(device)); - } - private bool Fits(float x, float y, bool ignoreOverlap) { if (x < 0 || y < 0) diff --git a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorView.axaml b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorView.axaml index ce05a97ff..fabfe1b86 100644 --- a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorView.axaml +++ b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorView.axaml @@ -5,8 +5,10 @@ xmlns:paz="clr-namespace:Avalonia.Controls.PanAndZoom;assembly=Avalonia.Controls.PanAndZoom" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" + xmlns:surfaceEditor="clr-namespace:Artemis.UI.Screens.SurfaceEditor" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Artemis.UI.Screens.SurfaceEditor.SurfaceEditorView"> + x:Class="Artemis.UI.Screens.SurfaceEditor.SurfaceEditorView" + x:DataType="surfaceEditor:SurfaceEditorViewModel"> @@ -18,106 +20,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - + + + + Color overlay devices + + + Overlay first LED only + + - - + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs index 774a2c369..573687c6d 100644 --- a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs +++ b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs @@ -3,13 +3,16 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Reactive; +using System.Reactive.Disposables; using System.Threading.Tasks; using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Extensions; using Artemis.UI.Ninject.Factories; +using Artemis.UI.Shared.Services; using Avalonia; using ReactiveUI; +using SkiaSharp; namespace Artemis.UI.Screens.SurfaceEditor; @@ -17,29 +20,67 @@ public class SurfaceEditorViewModel : MainScreenViewModel { private readonly IRgbService _rgbService; private readonly ISettingsService _settingsService; + private readonly IDeviceVmFactory _deviceVmFactory; + private readonly IWindowService _windowService; + private readonly IDeviceService _deviceService; private List? _initialSelection; private bool _saving; + private bool _colorFirstLedOnly; + private bool _colorDevices; + private double _overlayOpacity; public SurfaceEditorViewModel(IScreen hostScreen, + ICoreService coreService, IRgbService rgbService, ISurfaceVmFactory surfaceVmFactory, - ISettingsService settingsService) : base(hostScreen, "surface-editor") + ISettingsService settingsService, + IDeviceVmFactory deviceVmFactory, + IWindowService windowService, + IDeviceService deviceService) : base(hostScreen, "surface-editor") { _rgbService = rgbService; _settingsService = settingsService; + _deviceVmFactory = deviceVmFactory; + _windowService = windowService; + _deviceService = deviceService; + DisplayName = "Surface Editor"; SurfaceDeviceViewModels = new ObservableCollection(rgbService.EnabledDevices.OrderBy(d => d.ZIndex).Select(d => surfaceVmFactory.SurfaceDeviceViewModel(d, this))); ListDeviceViewModels = new ObservableCollection(rgbService.EnabledDevices.OrderBy(d => d.ZIndex).Select(d => surfaceVmFactory.ListDeviceViewModel(d, this))); + AutoArrange = ReactiveCommand.CreateFromTask(ExecuteAutoArrange); + IdentifyDevice = ReactiveCommand.Create(ExecuteIdentifyDevice); + ViewProperties = ReactiveCommand.CreateFromTask(ExecuteViewProperties); BringToFront = ReactiveCommand.Create(ExecuteBringToFront); BringForward = ReactiveCommand.Create(ExecuteBringForward); SendToBack = ReactiveCommand.Create(ExecuteSendToBack); SendBackward = ReactiveCommand.Create(ExecuteSendBackward); + + this.WhenActivated(d => + { + coreService.FrameRendering += CoreServiceOnFrameRendering; + Disposable.Create(() => coreService.FrameRendering -= CoreServiceOnFrameRendering).DisposeWith(d); + }); + } + + public bool ColorDevices + { + get => _colorDevices; + set => RaiseAndSetIfChanged(ref _colorDevices, value); + } + + public bool ColorFirstLedOnly + { + get => _colorFirstLedOnly; + set => RaiseAndSetIfChanged(ref _colorFirstLedOnly, value); } public ObservableCollection SurfaceDeviceViewModels { get; } public ObservableCollection ListDeviceViewModels { get; } + public ReactiveCommand AutoArrange { get; } + public ReactiveCommand IdentifyDevice { get; } + public ReactiveCommand ViewProperties { get; } public ReactiveCommand BringToFront { get; } public ReactiveCommand BringForward { get; } public ReactiveCommand SendToBack { get; } @@ -108,15 +149,63 @@ public class SurfaceEditorViewModel : MainScreenViewModel foreach (SurfaceDeviceViewModel surfaceDeviceViewModel in SurfaceDeviceViewModels) surfaceDeviceViewModel.UpdateMouseDrag(mousePosition, round, ignoreOverlap); } - - private void ApplySurfaceSelection() + + private async Task ExecuteAutoArrange() { - foreach (ListDeviceViewModel viewModel in ListDeviceViewModels) - viewModel.IsSelected = SurfaceDeviceViewModels.Any(s => s.Device == viewModel.Device && s.IsSelected); + bool confirmed = await _windowService.ShowConfirmContentDialog("Auto-arrange layout", "Are you sure you want to auto-arrange your layout? Your current settings will be overwritten."); + if (!confirmed) + return; + + _rgbService.AutoArrangeDevices(); + } + + private void CoreServiceOnFrameRendering(object? sender, FrameRenderingEventArgs e) + { + // Animate the overlay because I'm vain + if (ColorDevices && _overlayOpacity < 1) + _overlayOpacity = Math.Min(1, _overlayOpacity + e.DeltaTime * 3); + else if (!ColorDevices && _overlayOpacity > 0) + _overlayOpacity = Math.Max(0, _overlayOpacity - e.DeltaTime * 3); + + if (_overlayOpacity == 0) + return; + + using SKPaint paint = new(); + byte alpha = (byte) (Easings.CubicEaseInOut(_overlayOpacity) * 255); + + // Fill the entire canvas with a black backdrop + paint.Color = SKColors.Black.WithAlpha(alpha); + e.Canvas.DrawRect(e.Canvas.LocalClipBounds, paint); + + // Draw a rectangle for each LED + foreach (ListDeviceViewModel listDeviceViewModel in ListDeviceViewModels) + { + // Order by position to accurately get the first LED + List leds = listDeviceViewModel.Device.Leds.OrderBy(l => l.RgbLed.Location.Y).ThenBy(l => l.RgbLed.Location.X).ToList(); + for (int index = 0; index < leds.Count; index++) + { + ArtemisLed artemisLed = leds[index]; + if (ColorFirstLedOnly && index == 0 || !ColorFirstLedOnly) + { + paint.Color = listDeviceViewModel.Color.WithAlpha(alpha); + e.Canvas.DrawRect(artemisLed.AbsoluteRectangle, paint); + } + } + } } #region Context menu commands + private void ExecuteIdentifyDevice(ArtemisDevice device) + { + _deviceService.IdentifyDevice(device); + } + + private async Task ExecuteViewProperties(ArtemisDevice device) + { + await _windowService.ShowDialogAsync(_deviceVmFactory.DevicePropertiesViewModel(device)); + } + private void ExecuteBringToFront(ArtemisDevice device) { SurfaceDeviceViewModel surfaceDeviceViewModel = SurfaceDeviceViewModels.First(d => d.Device == device);