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

Core - Allow device providers to opt out of layout LED creation/removal

Device properties UI - Highlight selected LEDs in white
This commit is contained in:
Robert 2021-04-01 19:57:28 +02:00
parent 7000110dcb
commit 7a3be83bf6
11 changed files with 206 additions and 101 deletions

View File

@ -329,6 +329,13 @@ namespace Artemis.Core
/// <param name="removeExcessiveLeds">A boolean indicating whether to remove excess LEDs present in the device but missing in the layout</param>
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);

View File

@ -51,6 +51,18 @@ namespace Artemis.Core.DeviceProviders
/// </summary>
public bool CanDetectLogicalLayout { get; protected set; }
/// <summary>
/// Gets or sets a boolean indicating whether adding missing LEDs defined in a layout but missing on the device is supported
/// <para>Note: Defaults to <see langword="true" />.</para>
/// </summary>
public bool CreateMissingLedsSupported { get; protected set; } = true;
/// <summary>
/// Gets or sets a boolean indicating whether removing excess LEDs present in the device but missing in the layout is supported
/// <para>Note: Defaults to <see langword="true" />.</para>
/// </summary>
public bool RemoveExcessiveLedsSupported { get; protected set; } = true;
/// <summary>
/// Loads a layout for the specified device and wraps it in an <see cref="ArtemisLayout" />
/// </summary>

View File

@ -90,9 +90,7 @@ namespace Artemis.Core.Services
/// </summary>
/// <param name="device"></param>
/// <param name="layout"></param>
/// <param name="createMissingLeds"></param>
/// <param name="removeExessiveLeds"></param>
void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout layout, bool createMissingLeds, bool removeExessiveLeds);
void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout layout);
/// <summary>
/// Attempts to retrieve the <see cref="ArtemisDevice" /> that corresponds the provided RGB.NET

View File

@ -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();
}

View File

@ -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);
}
/// <summary>
/// Occurs when a LED of the device has been clicked
/// </summary>
public event EventHandler<LedClickedEventArgs>? LedClicked;
/// <inheritdoc />
protected override void OnRender(DrawingContext drawingContext)
{
@ -140,6 +144,27 @@ namespace Artemis.UI.Shared
return ResizeKeepAspect(deviceSize, availableSize.Width, availableSize.Height);
}
/// <summary>
/// Invokes the <see cref="LedClicked" /> event
/// </summary>
/// <param name="e"></param>
protected virtual void OnLedClicked(LedClickedEventArgs e)
{
LedClicked?.Invoke(this, e);
}
/// <summary>
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources.
/// </param>
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<LedClickedEventArgs>? LedClicked;
/// <summary>
/// Invokes the <see cref="LedClicked" /> event
/// </summary>
/// <param name="e"></param>
protected virtual void OnLedClicked(LedClickedEventArgs e)
{
LedClicked?.Invoke(this, e);
}
#endregion
#region IDisposable
/// <summary>
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
if (disposing) _timer.Stop();
}
/// <inheritdoc />
public void Dispose()
@ -353,7 +349,5 @@ namespace Artemis.UI.Shared
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}

View File

@ -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)

View File

@ -105,8 +105,8 @@
</VisualBrush>
</Grid.Background>
<!-- No need to provide LEDs to highlight as LEDs are already physically highlighted -->
<shared:DeviceVisualizer Device="{Binding Device}"
HighlightedLeds="{Binding SelectedLeds}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
ShowColors="True"

View File

@ -1,8 +1,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows.Input;
using System.Xml.Serialization;
using Artemis.Core;
using Artemis.Core.Services;
@ -13,20 +13,28 @@ using Artemis.UI.Shared.Services;
using MaterialDesignThemes.Wpf;
using Ookii.Dialogs.Wpf;
using RGB.NET.Layout;
using SkiaSharp;
using Stylet;
namespace Artemis.UI.Screens.Settings.Device
{
public class DeviceDialogViewModel : Conductor<Screen>.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<ArtemisLed> _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<ArtemisLed>();
}
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<ArtemisLed> SelectedLeds => SelectedLed != null ? new List<ArtemisLed> {SelectedLed} : null;
public BindableCollection<ArtemisLed> 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
}
}

View File

@ -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}}">
<UserControl.Resources>
<Converters:UriToFileNameConverter x:Key="UriToFileNameConverter"/>
<converters:UriToFileNameConverter x:Key="UriToFileNameConverter"/>
</UserControl.Resources>
<Grid>
<DataGrid ItemsSource="{Binding Device.Leds}"
d:DataContext="{d:DesignInstance Type={x:Type core:ArtemisLed}}"
<DataGrid ItemsSource="{Binding LedViewModels}"
d:DataContext="{d:DesignInstance Type={x:Type tabs:DeviceLedsTabLedViewModel}}"
CanUserSortColumns="True"
IsReadOnly="True"
CanUserAddRows="False"
AutoGenerateColumns="False"
materialDesign:DataGridAssist.CellPadding="5"
materialDesign:DataGridAssist.ColumnHeaderPadding="5"
SelectedItem="{Binding Parent.SelectedLed}"
CanUserResizeRows="False"
VirtualizingStackPanel.VirtualizationMode="Standard"
Margin="10">
<DataGrid.Resources>
<Style TargetType="DataGridRow" BasedOn="{StaticResource MaterialDesignDataGridRow}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<materialDesign:DataGridTextColumn Binding="{Binding RgbLed.Id}" Header="LED ID" Width="Auto" />
<materialDesign:DataGridTextColumn Binding="{Binding RgbLed.Color}" Header="Color (ARGB)" Width="Auto" />
<materialDesign:DataGridTextColumn Binding="{Binding Layout.Image, Converter={StaticResource UriToFileNameConverter}, Mode=OneWay}" Header="Image file" />
<materialDesign:DataGridTextColumn Binding="{Binding RgbLed.Shape}" Header="Shape" />
<materialDesign:DataGridTextColumn Binding="{Binding RgbLed.Size}" Header="Size" Width="Auto" />
<materialDesign:DataGridTextColumn Binding="{Binding RgbLed.CustomData}" Header="LED data" Width="Auto" />
<materialDesign:DataGridTextColumn Binding="{Binding ArtemisLed.RgbLed.Id}" Header="LED ID" Width="Auto" />
<materialDesign:DataGridTextColumn Binding="{Binding ArtemisLed.RgbLed.Color}" Header="Color (ARGB)" Width="Auto" />
<materialDesign:DataGridTextColumn Binding="{Binding ArtemisLed.Layout.Image, Converter={StaticResource UriToFileNameConverter}, Mode=OneWay}" Header="Image file" />
<materialDesign:DataGridTextColumn Binding="{Binding ArtemisLed.RgbLed.Shape}" Header="Shape" />
<materialDesign:DataGridTextColumn Binding="{Binding ArtemisLed.RgbLed.Size}" Header="Size" Width="Auto" />
<materialDesign:DataGridTextColumn Binding="{Binding ArtemisLed.RgbLed.CustomData}" Header="LED data" Width="Auto" />
</DataGrid.Columns>
</DataGrid>
</Grid>

View File

@ -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<DeviceLedsTabLedViewModel>();
}
public ArtemisDevice Device { get; }
public BindableCollection<DeviceLedsTabLedViewModel> LedViewModels { get; }
private void SelectedLedsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
UpdateSelectedLeds();
}
private void UpdateSelectedLeds()
{
foreach (DeviceLedsTabLedViewModel deviceLedsTabLedViewModel in LedViewModels)
deviceLedsTabLedViewModel.Update();
}
#region Overrides of Screen
/// <inheritdoc />
protected override void OnInitialActivate()
{
BindableCollection<ArtemisLed> selectedLeds = ((DeviceDialogViewModel) Parent).SelectedLeds;
LedViewModels.Clear();
LedViewModels.AddRange(Device.Leds.Select(l => new DeviceLedsTabLedViewModel(l, selectedLeds)));
selectedLeds.CollectionChanged += SelectedLedsOnCollectionChanged;
base.OnInitialActivate();
}
/// <inheritdoc />
protected override void OnClose()
{
((DeviceDialogViewModel) Parent).SelectedLeds.CollectionChanged -= SelectedLedsOnCollectionChanged;
base.OnClose();
}
#endregion
}
public class DeviceLedsTabLedViewModel : PropertyChangedBase
{
private readonly BindableCollection<ArtemisLed> _selectedLeds;
private bool _isSelected;
public DeviceLedsTabLedViewModel(ArtemisLed artemisLed, BindableCollection<ArtemisLed> 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);
}
}
}

View File

@ -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,