From 7a3be83bf6a1220353146a8dbf0e8b9e0ed78990 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 1 Apr 2021 19:57:28 +0200 Subject: [PATCH] Core - Allow device providers to opt out of layout LED creation/removal Device properties UI - Highlight selected LEDs in white --- .../Models/Surface/ArtemisDevice.cs | 7 ++ .../Plugins/DeviceProviders/DeviceProvider.cs | 12 +++ .../Services/Interfaces/IRgbService.cs | 4 +- src/Artemis.Core/Services/RgbService.cs | 12 +-- .../Controls/DeviceVisualizer.cs | 62 ++++++------ .../Tree/TreeGroupViewModel.cs | 2 +- .../Settings/Device/DeviceDialogView.xaml | 2 +- .../Settings/Device/DeviceDialogViewModel.cs | 97 +++++++++++-------- .../Device/Tabs/DeviceLedsTabView.xaml | 29 +++--- .../Device/Tabs/DeviceLedsTabViewModel.cs | 79 ++++++++++++++- .../Tabs/Plugins/PluginFeatureViewModel.cs | 1 - 11 files changed, 206 insertions(+), 101 deletions(-) diff --git a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs index d28885e65..0baf7d834 100644 --- a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs +++ b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs @@ -329,6 +329,13 @@ namespace Artemis.Core /// A boolean indicating whether to remove excess LEDs present in the device but missing in the layout internal void ApplyLayout(ArtemisLayout layout, bool createMissingLeds, bool removeExcessiveLeds) { + if (createMissingLeds && !DeviceProvider.CreateMissingLedsSupported) + throw new ArtemisCoreException($"Cannot apply layout with {nameof(createMissingLeds)} " + + "set to true because the device provider does not support it"); + if (removeExcessiveLeds && !DeviceProvider.RemoveExcessiveLedsSupported) + throw new ArtemisCoreException($"Cannot apply layout with {nameof(removeExcessiveLeds)} " + + "set to true because the device provider does not support it"); + if (layout.IsValid) layout.RgbLayout!.ApplyTo(RgbDevice, createMissingLeds, removeExcessiveLeds); diff --git a/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs b/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs index 949065503..f41e9c145 100644 --- a/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs +++ b/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs @@ -51,6 +51,18 @@ namespace Artemis.Core.DeviceProviders /// public bool CanDetectLogicalLayout { get; protected set; } + /// + /// Gets or sets a boolean indicating whether adding missing LEDs defined in a layout but missing on the device is supported + /// Note: Defaults to . + /// + public bool CreateMissingLedsSupported { get; protected set; } = true; + + /// + /// Gets or sets a boolean indicating whether removing excess LEDs present in the device but missing in the layout is supported + /// Note: Defaults to . + /// + public bool RemoveExcessiveLedsSupported { get; protected set; } = true; + /// /// Loads a layout for the specified device and wraps it in an /// diff --git a/src/Artemis.Core/Services/Interfaces/IRgbService.cs b/src/Artemis.Core/Services/Interfaces/IRgbService.cs index 00c5f85d0..cfcb5ffb0 100644 --- a/src/Artemis.Core/Services/Interfaces/IRgbService.cs +++ b/src/Artemis.Core/Services/Interfaces/IRgbService.cs @@ -90,9 +90,7 @@ namespace Artemis.Core.Services /// /// /// - /// - /// - void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout layout, bool createMissingLeds, bool removeExessiveLeds); + void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout layout); /// /// Attempts to retrieve the that corresponds the provided RGB.NET diff --git a/src/Artemis.Core/Services/RgbService.cs b/src/Artemis.Core/Services/RgbService.cs index 4f812c886..5e4a5066a 100644 --- a/src/Artemis.Core/Services/RgbService.cs +++ b/src/Artemis.Core/Services/RgbService.cs @@ -312,7 +312,7 @@ namespace Artemis.Core.Services layout = new ArtemisLayout(device.CustomLayoutPath, LayoutSource.Configured); if (layout.IsValid) { - ApplyDeviceLayout(device, layout, true, true); + ApplyDeviceLayout(device, layout); return layout; } } @@ -321,7 +321,7 @@ namespace Artemis.Core.Services layout = device.DeviceProvider.LoadUserLayout(device); if (layout.IsValid) { - ApplyDeviceLayout(device, layout, true, true); + ApplyDeviceLayout(device, layout); return layout; } @@ -329,13 +329,13 @@ namespace Artemis.Core.Services layout = device.DeviceProvider.LoadLayout(device); if (layout.IsValid) { - ApplyDeviceLayout(device, layout, true, true); + ApplyDeviceLayout(device, layout); return layout; } // Finally fall back to a default layout layout = LoadDefaultLayout(device); - ApplyDeviceLayout(device, layout, true, true); + ApplyDeviceLayout(device, layout); return layout; } @@ -344,9 +344,9 @@ namespace Artemis.Core.Services return new("NYI", LayoutSource.Default); } - public void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout layout, bool createMissingLeds, bool removeExessiveLeds) + public void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout layout) { - device.ApplyLayout(layout, createMissingLeds, removeExessiveLeds); + device.ApplyLayout(layout, device.DeviceProvider.CreateMissingLedsSupported, device.DeviceProvider.RemoveExcessiveLedsSupported); UpdateLedGroup(); } diff --git a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs index bc4b3648f..6dce06e42 100644 --- a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs +++ b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs @@ -10,7 +10,6 @@ using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using Artemis.Core; -using SkiaSharp; using Stylet; namespace Artemis.UI.Shared @@ -88,6 +87,11 @@ namespace Artemis.UI.Shared set => SetValue(HighlightedLedsProperty, value); } + /// + /// Occurs when a LED of the device has been clicked + /// + public event EventHandler? LedClicked; + /// protected override void OnRender(DrawingContext drawingContext) { @@ -140,6 +144,27 @@ namespace Artemis.UI.Shared return ResizeKeepAspect(deviceSize, availableSize.Width, availableSize.Height); } + /// + /// Invokes the event + /// + /// + protected virtual void OnLedClicked(LedClickedEventArgs e) + { + LedClicked?.Invoke(this, e); + } + + /// + /// Releases the unmanaged resources used by the object and optionally releases the managed resources. + /// + /// + /// to release both managed and unmanaged resources; + /// to release only unmanaged resources. + /// + protected virtual void Dispose(bool disposing) + { + if (disposing) _timer.Stop(); + } + private static Size ResizeKeepAspect(Size src, double maxWidth, double maxHeight) { @@ -189,8 +214,8 @@ namespace Artemis.UI.Shared return; Point position = e.GetPosition(this); - double x = (position.X / RenderSize.Width); - double y = (position.Y / RenderSize.Height); + double x = position.X / RenderSize.Width; + double y = position.Y / RenderSize.Height; Point scaledPosition = new(x * Device.Rectangle.Width, y * Device.Rectangle.Height); DeviceVisualizerLed? deviceVisualizerLed = _deviceVisualizerLeds.FirstOrDefault(l => l.DisplayGeometry != null && l.LedRect.Contains(scaledPosition)); @@ -317,35 +342,6 @@ namespace Artemis.UI.Shared drawingContext.Close(); } - #region Events - - public event EventHandler? LedClicked; - - /// - /// Invokes the event - /// - /// - protected virtual void OnLedClicked(LedClickedEventArgs e) - { - LedClicked?.Invoke(this, e); - } - - #endregion - - #region IDisposable - - /// - /// Releases the unmanaged resources used by the object and optionally releases the managed resources. - /// - /// - /// to release both managed and unmanaged resources; - /// to release only unmanaged resources. - /// - protected virtual void Dispose(bool disposing) - { - if (disposing) _timer.Stop(); - } - /// public void Dispose() @@ -353,7 +349,5 @@ namespace Artemis.UI.Shared Dispose(true); GC.SuppressFinalize(this); } - - #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeGroupViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeGroupViewModel.cs index 76c54481b..30d8fd822 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeGroupViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeGroupViewModel.cs @@ -32,7 +32,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree private readonly IDialogService _dialogService; private readonly IProfileEditorService _profileEditorService; private readonly IWindowManager _windowManager; - private LayerBrushSettingsWindowViewModel? _layerBrushSettingsWindowVm; + private LayerBrushSettingsWindowViewModel _layerBrushSettingsWindowVm; private LayerEffectSettingsWindowViewModel _layerEffectSettingsWindowVm; public TreeGroupViewModel(LayerPropertyGroupViewModel layerPropertyGroupViewModel, IProfileEditorService profileEditorService, IDialogService dialogService, IWindowManager windowManager) diff --git a/src/Artemis.UI/Screens/Settings/Device/DeviceDialogView.xaml b/src/Artemis.UI/Screens/Settings/Device/DeviceDialogView.xaml index 8973e4109..8134150e7 100644 --- a/src/Artemis.UI/Screens/Settings/Device/DeviceDialogView.xaml +++ b/src/Artemis.UI/Screens/Settings/Device/DeviceDialogView.xaml @@ -105,8 +105,8 @@ + .Collection.OneActive { + private readonly ICoreService _coreService; private readonly IDeviceService _deviceService; private readonly IDialogService _dialogService; private readonly IRgbService _rgbService; - private ArtemisLed _selectedLed; private SnackbarMessageQueue _deviceMessageQueue; + private BindableCollection _selectedLeds; - public DeviceDialogViewModel(ArtemisDevice device, IDeviceService deviceService, IRgbService rgbService, IDialogService dialogService, IDeviceDebugVmFactory factory) + public DeviceDialogViewModel(ArtemisDevice device, + ICoreService coreService, + IDeviceService deviceService, + IRgbService rgbService, + IDialogService dialogService, + IDeviceDebugVmFactory factory) { + _coreService = coreService; _deviceService = deviceService; _rgbService = rgbService; _dialogService = dialogService; @@ -39,19 +47,8 @@ namespace Artemis.UI.Screens.Settings.Device Items.Add(factory.DeviceLedsTabViewModel(device)); ActiveItem = Items.First(); DisplayName = $"{device.RgbDevice.DeviceInfo.Model} | Artemis"; - } - protected override void OnInitialActivate() - { - DeviceMessageQueue = new SnackbarMessageQueue(TimeSpan.FromSeconds(5)); - Device.DeviceUpdated += DeviceOnDeviceUpdated; - base.OnInitialActivate(); - } - - protected override void OnClose() - { - Device.DeviceUpdated -= DeviceOnDeviceUpdated; - base.OnClose(); + SelectedLeds = new BindableCollection(); } public ArtemisDevice Device { get; } @@ -63,29 +60,61 @@ namespace Artemis.UI.Screens.Settings.Device set => SetAndNotify(ref _deviceMessageQueue, value); } - public ArtemisLed SelectedLed - { - get => _selectedLed; - set - { - if (!SetAndNotify(ref _selectedLed, value)) return; - NotifyOfPropertyChange(nameof(SelectedLeds)); - } - } - public bool CanExportLayout => Device.Layout?.IsValid ?? false; - public List SelectedLeds => SelectedLed != null ? new List {SelectedLed} : null; + public BindableCollection SelectedLeds + { + get => _selectedLeds; + set => SetAndNotify(ref _selectedLeds, value); + } public bool CanOpenImageDirectory => Device.Layout?.Image != null; + public void OnLedClicked(object sender, LedClickedEventArgs e) + { + if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift)) + SelectedLeds.Clear(); + SelectedLeds.Add(e.Led); + } + + protected override void OnInitialActivate() + { + _coreService.FrameRendering += CoreServiceOnFrameRendering; + DeviceMessageQueue = new SnackbarMessageQueue(TimeSpan.FromSeconds(5)); + Device.DeviceUpdated += DeviceOnDeviceUpdated; + base.OnInitialActivate(); + } + + protected override void OnClose() + { + _coreService.FrameRendering -= CoreServiceOnFrameRendering; + Device.DeviceUpdated -= DeviceOnDeviceUpdated; + base.OnClose(); + } + + private void CoreServiceOnFrameRendering(object sender, FrameRenderingEventArgs e) + { + if (SelectedLeds == null) + 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); + } + + private void DeviceOnDeviceUpdated(object sender, EventArgs e) + { + NotifyOfPropertyChange(nameof(CanExportLayout)); + } + // ReSharper disable UnusedMember.Global #region Command handlers public void ClearSelection() { - SelectedLed = null; + SelectedLeds.Clear(); } public void IdentifyDevice() @@ -190,19 +219,5 @@ namespace Artemis.UI.Screens.Settings.Device #endregion // ReSharper restore UnusedMember.Global - - #region Event handlers - - private void DeviceOnDeviceUpdated(object sender, EventArgs e) - { - NotifyOfPropertyChange(nameof(CanExportLayout)); - } - - public void OnLedClicked(object sender, LedClickedEventArgs e) - { - SelectedLed = e.Led; - } - - #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceLedsTabView.xaml b/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceLedsTabView.xaml index 08b374383..6bfec663f 100644 --- a/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceLedsTabView.xaml +++ b/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceLedsTabView.xaml @@ -4,34 +4,39 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" - xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core" xmlns:tabs="clr-namespace:Artemis.UI.Screens.Settings.Device.Tabs" - xmlns:Converters="clr-namespace:Artemis.UI.Converters" x:Class="Artemis.UI.Screens.Settings.Device.Tabs.DeviceLedsTabView" + xmlns:converters="clr-namespace:Artemis.UI.Converters" + x:Class="Artemis.UI.Screens.Settings.Device.Tabs.DeviceLedsTabView" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance {x:Type tabs:DeviceLedsTabViewModel}}"> - + - + + + - - - - - - + + + + + + diff --git a/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceLedsTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceLedsTabViewModel.cs index d57f8cb1a..5ec8986f7 100644 --- a/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceLedsTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceLedsTabViewModel.cs @@ -1,17 +1,92 @@ -using Artemis.Core; +using System.Collections.Specialized; +using System.Linq; +using Artemis.Core; using Stylet; namespace Artemis.UI.Screens.Settings.Device.Tabs { public class DeviceLedsTabViewModel : Screen { - public DeviceLedsTabViewModel(ArtemisDevice device) { Device = device; DisplayName = "LEDS"; + LedViewModels = new BindableCollection(); } public ArtemisDevice Device { get; } + public BindableCollection LedViewModels { get; } + + private void SelectedLedsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + UpdateSelectedLeds(); + } + + private void UpdateSelectedLeds() + { + foreach (DeviceLedsTabLedViewModel deviceLedsTabLedViewModel in LedViewModels) + deviceLedsTabLedViewModel.Update(); + } + + #region Overrides of Screen + + /// + protected override void OnInitialActivate() + { + BindableCollection selectedLeds = ((DeviceDialogViewModel) Parent).SelectedLeds; + LedViewModels.Clear(); + LedViewModels.AddRange(Device.Leds.Select(l => new DeviceLedsTabLedViewModel(l, selectedLeds))); + selectedLeds.CollectionChanged += SelectedLedsOnCollectionChanged; + + base.OnInitialActivate(); + } + + /// + protected override void OnClose() + { + ((DeviceDialogViewModel) Parent).SelectedLeds.CollectionChanged -= SelectedLedsOnCollectionChanged; + base.OnClose(); + } + + #endregion + } + + public class DeviceLedsTabLedViewModel : PropertyChangedBase + { + private readonly BindableCollection _selectedLeds; + private bool _isSelected; + + public DeviceLedsTabLedViewModel(ArtemisLed artemisLed, BindableCollection selectedLeds) + { + _selectedLeds = selectedLeds; + ArtemisLed = artemisLed; + + Update(); + } + + public ArtemisLed ArtemisLed { get; } + + public bool IsSelected + { + get => _isSelected; + set + { + if (!SetAndNotify(ref _isSelected, value)) return; + Apply(); + } + } + + public void Update() + { + IsSelected = _selectedLeds.Contains(ArtemisLed); + } + + public void Apply() + { + if (IsSelected && !_selectedLeds.Contains(ArtemisLed)) + _selectedLeds.Add(ArtemisLed); + else if (!IsSelected && _selectedLeds.Contains(ArtemisLed)) + _selectedLeds.Remove(ArtemisLed); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs index a6a80b341..a4e8587dc 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs @@ -16,7 +16,6 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins private readonly IPluginManagementService _pluginManagementService; private bool _enabling; private readonly IMessageService _messageService; - private bool _canToggleEnabled; public PluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo, bool showShield,