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

Merge branch 'development'

This commit is contained in:
Robert 2021-04-10 12:53:43 +02:00
commit fbf04b53db
25 changed files with 441 additions and 53 deletions

View File

@ -15,7 +15,7 @@ Artemis 1 is no longer supported and Artemis 2 is in active development. This en
**Pre-release download**: https://github.com/SpoinkyNL/Artemis/releases (pre-release means your profiles may break at any given time!)
**Plugin documentation**: https://artemis-rgb.com/docs/
**Please note that even though we have plugins for each brand supported by RGB.NET, they have not been thoroughly tested. If you run into any issues please let us know on Discord.**
**Please note that even though we have plugins for each brand supported by RGB.NET, they have not been thoroughly tested due to a lack of hardware. If you run into any issues please let us know on Discord.**
A full list of supported devices can be found on the wiki [here](https://wiki.artemis-rgb.com/en/guides/user/devices).
#### Want to build? Follow these instructions

View File

@ -9,6 +9,7 @@
RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, new FloatDataBindingConverter<FloatRange>(), "End");
CurrentValueSet += OnCurrentValueSet;
DefaultValue = new FloatRange();
}
/// <inheritdoc />
@ -25,7 +26,7 @@
private void OnCurrentValueSet(object? sender, LayerPropertyEventArgs e)
{
// Don't allow the int range to be null
BaseValue ??= DefaultValue ?? new FloatRange(0, 0);
BaseValue ??= DefaultValue ?? new FloatRange();
}
}
}

View File

@ -9,6 +9,7 @@
RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, new IntDataBindingConverter<IntRange>(), "End");
CurrentValueSet += OnCurrentValueSet;
DefaultValue = new IntRange();
}
/// <inheritdoc />
@ -25,7 +26,7 @@
private void OnCurrentValueSet(object? sender, LayerPropertyEventArgs e)
{
// Don't allow the int range to be null
BaseValue ??= DefaultValue ?? new IntRange(0, 0);
BaseValue ??= DefaultValue ?? new IntRange();
}
}
}

View File

@ -9,6 +9,14 @@ namespace Artemis.Core
{
private readonly Random _rand;
/// <summary>
/// Creates a new instance of the <see cref="FloatRange" /> class
/// </summary>
public FloatRange()
{
_rand = new Random();
}
/// <summary>
/// Creates a new instance of the <see cref="FloatRange" /> class
/// </summary>

View File

@ -9,6 +9,14 @@ namespace Artemis.Core
{
private readonly Random _rand;
/// <summary>
/// Creates a new instance of the <see cref="IntRange" /> class
/// </summary>
public IntRange()
{
_rand = new Random();
}
/// <summary>
/// Creates a new instance of the <see cref="IntRange" /> class
/// </summary>

View File

@ -34,7 +34,15 @@ namespace Artemis.Core
CurrentValue = default!;
DefaultValue = default!;
_baseValue = default!;
// We'll try our best...
// TODO: Consider alternatives
if (typeof(T).IsValueType)
_baseValue = default!;
else if (typeof(T).GetConstructor(Type.EmptyTypes) != null)
_baseValue = Activator.CreateInstance<T>();
else
_baseValue = default!;
_keyframes = new List<LayerPropertyKeyframe<T>>();
}

View File

@ -35,10 +35,9 @@ namespace Artemis.Core
IsEnabled = true;
InputIdentifiers = new List<ArtemisDeviceInputIdentifier>();
InputMappings = new Dictionary<ArtemisLed, ArtemisLed>();
Leds = rgbDevice.Select(l => new ArtemisLed(l, this)).ToList().AsReadOnly();
LedIds = new ReadOnlyDictionary<LedId, ArtemisLed>(Leds.ToDictionary(l => l.RgbLed.Id, l => l));
UpdateLeds();
ApplyKeyboardLayout();
ApplyToEntity();
CalculateRenderProperties();
@ -52,12 +51,12 @@ namespace Artemis.Core
DeviceProvider = deviceProvider;
InputIdentifiers = new List<ArtemisDeviceInputIdentifier>();
InputMappings = new Dictionary<ArtemisLed, ArtemisLed>();
foreach (DeviceInputIdentifierEntity identifierEntity in DeviceEntity.InputIdentifiers)
InputIdentifiers.Add(new ArtemisDeviceInputIdentifier(identifierEntity.InputProvider, identifierEntity.Identifier));
Leds = rgbDevice.Select(l => new ArtemisLed(l, this)).ToList().AsReadOnly();
LedIds = new ReadOnlyDictionary<LedId, ArtemisLed>(Leds.ToDictionary(l => l.RgbLed.Id, l => l));
UpdateLeds();
ApplyKeyboardLayout();
}
@ -110,6 +109,8 @@ namespace Artemis.Core
/// </summary>
public List<ArtemisDeviceInputIdentifier> InputIdentifiers { get; }
public Dictionary<ArtemisLed, ArtemisLed> InputMappings { get; }
/// <summary>
/// Gets or sets the X-position of the device
/// </summary>
@ -287,20 +288,27 @@ namespace Artemis.Core
/// Attempts to retrieve the <see cref="ArtemisLed" /> that corresponds the provided RGB.NET <see cref="Led" />
/// </summary>
/// <param name="led">The RGB.NET <see cref="Led" /> to find the corresponding <see cref="ArtemisLed" /> for </param>
/// <param name="applyInputMapping">If <see langword="true"/>, LEDs mapped to different LEDs <see cref="InputMappings"/> are taken into consideration</param>
/// <returns>If found, the corresponding <see cref="ArtemisLed" />; otherwise <see langword="null" />.</returns>
public ArtemisLed? GetLed(Led led)
public ArtemisLed? GetLed(Led led, bool applyInputMapping)
{
return GetLed(led.Id);
return GetLed(led.Id, applyInputMapping);
}
/// <summary>
/// Attempts to retrieve the <see cref="ArtemisLed" /> that corresponds the provided RGB.NET <see cref="LedId" />
/// </summary>
/// <param name="ledId">The RGB.NET <see cref="LedId" /> to find the corresponding <see cref="ArtemisLed" /> for </param>
/// <param name="applyInputMapping">If <see langword="true"/>, LEDs mapped to different LEDs <see cref="InputMappings"/> are taken into consideration</param>
/// <returns>If found, the corresponding <see cref="ArtemisLed" />; otherwise <see langword="null" />.</returns>
public ArtemisLed? GetLed(LedId ledId)
public ArtemisLed? GetLed(LedId ledId, bool applyInputMapping)
{
LedIds.TryGetValue(ledId, out ArtemisLed? artemisLed);
if (artemisLed == null)
return null;
if (applyInputMapping && InputMappings.TryGetValue(artemisLed, out ArtemisLed? mappedLed))
return mappedLed;
return artemisLed;
}
@ -339,8 +347,7 @@ namespace Artemis.Core
if (layout.IsValid)
layout.RgbLayout!.ApplyTo(RgbDevice, createMissingLeds, removeExcessiveLeds);
Leds = RgbDevice.Select(l => new ArtemisLed(l, this)).ToList().AsReadOnly();
LedIds = new ReadOnlyDictionary<LedId, ArtemisLed>(Leds.ToDictionary(l => l.RgbLed.Id, l => l));
UpdateLeds();
Layout = layout;
Layout.ApplyDevice(this);
@ -348,6 +355,21 @@ namespace Artemis.Core
OnDeviceUpdated();
}
private void UpdateLeds()
{
Leds = RgbDevice.Select(l => new ArtemisLed(l, this)).ToList().AsReadOnly();
LedIds = new ReadOnlyDictionary<LedId, ArtemisLed>(Leds.ToDictionary(l => l.RgbLed.Id, l => l));
InputMappings.Clear();
foreach (InputMappingEntity deviceEntityInputMapping in DeviceEntity.InputMappings)
{
ArtemisLed? original = Leds.FirstOrDefault(l => l.RgbLed.Id == (LedId) deviceEntityInputMapping.OriginalLedId);
ArtemisLed? mapped = Leds.FirstOrDefault(l => l.RgbLed.Id == (LedId) deviceEntityInputMapping.MappedLedId);
if (original != null && mapped != null)
InputMappings.Add(original, mapped);
}
}
internal void ApplyToEntity()
{
// Other properties are computed
@ -362,6 +384,10 @@ namespace Artemis.Core
Identifier = identifier.Identifier
});
}
DeviceEntity.InputMappings.Clear();
foreach (var (original, mapped) in InputMappings)
DeviceEntity.InputMappings.Add(new InputMappingEntity {OriginalLedId = (int) original.RgbLed.Id, MappedLedId = (int) mapped.RgbLed.Id});
}
internal void ApplyToRgbDevice()

View File

@ -1,4 +1,5 @@
using Artemis.Core.Services;
using RGB.NET.Core;
namespace Artemis.Core
{
@ -12,7 +13,7 @@ namespace Artemis.Core
/// </summary>
/// <param name="inputProvider">The full type and namespace of the <see cref="Services.InputProvider" /> this identifier is used by</param>
/// <param name="identifier">A value used to identify the device</param>
public ArtemisDeviceInputIdentifier(string inputProvider, object identifier)
internal ArtemisDeviceInputIdentifier(string inputProvider, object identifier)
{
InputProvider = inputProvider;
Identifier = identifier;
@ -28,4 +29,16 @@ namespace Artemis.Core
/// </summary>
public object Identifier { get; set; }
}
public class ArtemisDeviceInputMapping
{
public ArtemisLed OriginalLed { get; }
public ArtemisLed MappedLed { get; }
internal ArtemisDeviceInputMapping(ArtemisLed originalLed, ArtemisLed mappedLed)
{
OriginalLed = originalLed;
MappedLed = mappedLed;
}
}
}

View File

@ -3,9 +3,15 @@ using RGB.NET.Core;
namespace Artemis.Core.Services
{
internal static class InputKeyUtilities
/// <summary>
/// Utilities for mapping keys and buttons to LEDs
/// </summary>
public static class InputKeyUtilities
{
internal static readonly Dictionary<KeyboardKey, LedId> KeyboardKeyLedIdMap = new()
/// <summary>
/// A dictionary of mappings between <see cref="KeyboardKey" /> and <see cref="LedId" />
/// </summary>
public static readonly Dictionary<KeyboardKey, LedId> KeyboardKeyLedIdMap = new()
{
{KeyboardKey.None, LedId.Keyboard_Custom1},
{KeyboardKey.Cancel, LedId.Keyboard_Custom2},
@ -182,7 +188,10 @@ namespace Artemis.Core.Services
{KeyboardKey.NumPadEnter, LedId.Keyboard_NumEnter}
};
internal static readonly Dictionary<MouseButton, LedId> MouseButtonLedIdMap = new()
/// <summary>
/// A dictionary of mappings between <see cref="MouseButton" /> and <see cref="LedId" />
/// </summary>
public static readonly Dictionary<MouseButton, LedId> MouseButtonLedIdMap = new()
{
{MouseButton.Left, LedId.Mouse1},
{MouseButton.Middle, LedId.Mouse2},

View File

@ -201,7 +201,7 @@ namespace Artemis.Core.Services
bool foundLedId = InputKeyUtilities.KeyboardKeyLedIdMap.TryGetValue(e.Key, out LedId ledId);
ArtemisLed? led = null;
if (foundLedId && e.Device != null)
led = e.Device.GetLed(ledId);
led = e.Device.GetLed(ledId, true);
// Create the UpDown event args because it can be used for every event
ArtemisKeyboardKeyUpDownEventArgs eventArgs = new(e.Device, led, e.Key, keyboardModifierKey, e.IsDown);

View File

@ -7,6 +7,7 @@ namespace Artemis.Storage.Entities.Surface
public DeviceEntity()
{
InputIdentifiers = new List<DeviceInputIdentifierEntity>();
InputMappings = new List<InputMappingEntity>();
}
public string Id { get; set; }
@ -25,9 +26,16 @@ namespace Artemis.Storage.Entities.Surface
public string CustomLayoutPath { get; set; }
public List<DeviceInputIdentifierEntity> InputIdentifiers { get; set; }
public List<InputMappingEntity> InputMappings { get; set; }
}
public class InputMappingEntity
{
public int OriginalLedId { get; set; }
public int MappedLedId { get; set; }
}
public class DeviceInputIdentifierEntity
{
public string InputProvider { get; set; }

View File

@ -6,6 +6,18 @@ namespace Artemis.UI.Shared.LayerBrushes
/// <inheritdoc />
public class LayerBrushConfigurationDialog<T> : LayerBrushConfigurationDialog where T : BrushConfigurationViewModel
{
/// <inheritdoc />
public LayerBrushConfigurationDialog()
{
}
/// <inheritdoc />
public LayerBrushConfigurationDialog(int dialogWidth, int dialogHeight)
{
DialogWidth = dialogWidth;
DialogHeight = dialogHeight;
}
/// <inheritdoc />
public override Type Type => typeof(T);
}
@ -15,6 +27,16 @@ namespace Artemis.UI.Shared.LayerBrushes
/// </summary>
public abstract class LayerBrushConfigurationDialog : ILayerBrushConfigurationDialog
{
/// <summary>
/// The default width of the dialog
/// </summary>
public int DialogWidth { get; set; } = 800;
/// <summary>
/// The default height of the dialog
/// </summary>
public int DialogHeight { get; set; } = 800;
/// <summary>
/// The type of view model the tab contains
/// </summary>

View File

@ -6,6 +6,19 @@ namespace Artemis.UI.Shared.LayerEffects
/// <inheritdoc />
public class LayerEffectConfigurationDialog<T> : LayerEffectConfigurationDialog where T : EffectConfigurationViewModel
{
/// <inheritdoc />
public LayerEffectConfigurationDialog()
{
}
/// <inheritdoc />
public LayerEffectConfigurationDialog(int dialogWidth, int dialogHeight)
{
DialogWidth = dialogWidth;
DialogHeight = dialogHeight;
}
/// <inheritdoc />
public override Type Type => typeof(T);
}
@ -15,11 +28,15 @@ namespace Artemis.UI.Shared.LayerEffects
/// </summary>
public abstract class LayerEffectConfigurationDialog : ILayerEffectConfigurationDialog
{
// TODO: See if this is still in use
/// <summary>
/// The layer effect this dialog belongs to
/// The default width of the dialog
/// </summary>
public BaseLayerEffect? LayerEffect { get; set; }
public int DialogWidth { get; set; } = 800;
/// <summary>
/// The default height of the dialog
/// </summary>
public int DialogHeight { get; set; } = 800;
/// <summary>
/// The type of view model the tab contains

View File

@ -49,6 +49,7 @@ namespace Artemis.UI.Ninject.Factories
DevicePropertiesTabViewModel DevicePropertiesTabViewModel(ArtemisDevice device);
DeviceInfoTabViewModel DeviceInfoTabViewModel(ArtemisDevice device);
DeviceLedsTabViewModel DeviceLedsTabViewModel(ArtemisDevice device);
InputMappingsTabViewModel InputMappingsTabViewModel(ArtemisDevice device);
}
public interface IProfileTreeVmFactory : IVmFactory

View File

@ -75,8 +75,11 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree
ConstructorArgument argument = new(brushParameter.Name, layerBrush);
BrushConfigurationViewModel viewModel = (BrushConfigurationViewModel) layerBrush.Descriptor.Provider.Plugin.Kernel.Get(configurationViewModel.Type, argument);
_layerBrushSettingsWindowVm = new LayerBrushSettingsWindowViewModel(viewModel);
_layerBrushSettingsWindowVm = new LayerBrushSettingsWindowViewModel(viewModel, configurationViewModel);
_windowManager.ShowDialog(_layerBrushSettingsWindowVm);
// Save changes after the dialog closes
_profileEditorService.UpdateSelectedProfile();
}
catch (Exception e)
{
@ -102,8 +105,11 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree
ConstructorArgument argument = new(effectParameter.Name, layerEffect);
EffectConfigurationViewModel viewModel = (EffectConfigurationViewModel) layerEffect.Descriptor.Provider.Plugin.Kernel.Get(configurationViewModel.Type, argument);
_layerEffectSettingsWindowVm = new LayerEffectSettingsWindowViewModel(viewModel);
_layerEffectSettingsWindowVm = new LayerEffectSettingsWindowViewModel(viewModel, configurationViewModel);
_windowManager.ShowDialog(_layerEffectSettingsWindowVm);
// Save changes after the dialog closes
_profileEditorService.UpdateSelectedProfile();
}
catch (Exception e)
{

View File

@ -12,8 +12,8 @@
Background="{DynamicResource MaterialDesignPaper}"
FontFamily="pack://application:,,,/MaterialDesignThemes.Wpf;component/Resources/Roboto/#Roboto"
UseLayoutRounding="True"
Width="800"
Height="800"
Width="800"
d:DesignHeight="800"
d:DesignWidth="800"
d:DataContext="{d:DesignInstance windows:LayerBrushSettingsWindowViewModel}"

View File

@ -1,4 +1,5 @@
using System;
using System.Windows;
using Artemis.UI.Shared.LayerBrushes;
using Stylet;
@ -6,12 +7,36 @@ namespace Artemis.UI.Screens.ProfileEditor.Windows
{
public class LayerBrushSettingsWindowViewModel : Conductor<BrushConfigurationViewModel>
{
public LayerBrushSettingsWindowViewModel(BrushConfigurationViewModel configurationViewModel)
private readonly LayerBrushConfigurationDialog _configuration;
public LayerBrushSettingsWindowViewModel(BrushConfigurationViewModel configurationViewModel, LayerBrushConfigurationDialog configuration)
{
_configuration = configuration;
ActiveItem = configurationViewModel ?? throw new ArgumentNullException(nameof(configurationViewModel));
ActiveItem.Closed += ActiveItemOnClosed;
}
#region Overrides of Screen
/// <inheritdoc />
protected override void OnViewLoaded()
{
// Setting the width/height via a binding and depending on WindowStartupLocation does not work
Window window = View as Window;
Window mainWindow = Application.Current.MainWindow;
if (window == null || mainWindow == null)
return;
window.Width = _configuration.DialogWidth;
window.Height = _configuration.DialogHeight;
window.Left = mainWindow.Left + (mainWindow.Width - window.Width) / 2;
window.Top = mainWindow.Top + (mainWindow.Height - window.Height) / 2;
base.OnViewLoaded();
}
#endregion
private void ActiveItemOnClosed(object sender, CloseEventArgs e)
{
ActiveItem.Closed -= ActiveItemOnClosed;

View File

@ -12,6 +12,8 @@
Background="{DynamicResource MaterialDesignPaper}"
FontFamily="pack://application:,,,/MaterialDesignThemes.Wpf;component/Resources/Roboto/#Roboto"
UseLayoutRounding="True"
MinWidth="400"
MinHeight="400"
Width="800"
Height="800"
d:DesignHeight="800"

View File

@ -1,4 +1,5 @@
using System;
using System.Windows;
using Artemis.UI.Shared.LayerEffects;
using Stylet;
@ -6,12 +7,36 @@ namespace Artemis.UI.Screens.ProfileEditor.Windows
{
public class LayerEffectSettingsWindowViewModel : Conductor<EffectConfigurationViewModel>
{
public LayerEffectSettingsWindowViewModel(EffectConfigurationViewModel configurationViewModel)
private LayerEffectConfigurationDialog _configuration;
public LayerEffectSettingsWindowViewModel(EffectConfigurationViewModel configurationViewModel, LayerEffectConfigurationDialog configuration)
{
_configuration = configuration;
ActiveItem = configurationViewModel ?? throw new ArgumentNullException(nameof(configurationViewModel));
ActiveItem.Closed += ActiveItemOnClosed;
}
#region Overrides of Screen
/// <inheritdoc />
protected override void OnViewLoaded()
{
// Setting the width/height via a binding and depending on WindowStartupLocation does not work
Window window = View as Window;
Window mainWindow = Application.Current.MainWindow;
if (window == null || mainWindow == null)
return;
window.Width = _configuration.DialogWidth;
window.Height = _configuration.DialogHeight;
window.Left = mainWindow.Left + (mainWindow.Width - window.Width) / 2;
window.Top = mainWindow.Top + (mainWindow.Height - window.Height) / 2;
base.OnViewLoaded();
}
#endregion
private void ActiveItemOnClosed(object sender, CloseEventArgs e)
{
ActiveItem.Closed -= ActiveItemOnClosed;

View File

@ -21,6 +21,9 @@
d:DesignHeight="800" d:DesignWidth="800"
d:DataContext="{d:DesignInstance device:DeviceDialogViewModel}"
Icon="/Resources/Images/Logo/logo-512.png">
<mde:MaterialWindow.Resources>
<shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter"/>
</mde:MaterialWindow.Resources>
<mde:MaterialWindow.InputBindings>
<KeyBinding Command="{s:Action ClearSelection}" Key="Escape" />
</mde:MaterialWindow.InputBindings>
@ -111,6 +114,14 @@
VerticalAlignment="Center"
ShowColors="True"
LedClicked="{s:Action OnLedClicked}"/>
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}"
Visibility="{Binding Device.Layout.RgbLayout.Author, Converter={StaticResource NullToVisibilityConverter}}"
VerticalAlignment="Bottom"
HorizontalAlignment="Left"
Margin="15">
Device layout by <Run FontWeight="Bold" Text="{Binding Device.Layout.RgbLayout.Author}"/>
</TextBlock>
</Grid>
<GridSplitter Grid.Column="1" Width="15" Margin="-15 0 0 0" Background="Transparent" HorizontalAlignment="Stretch" Panel.ZIndex="3" />

View File

@ -12,6 +12,7 @@ using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using MaterialDesignThemes.Wpf;
using Ookii.Dialogs.Wpf;
using RGB.NET.Core;
using RGB.NET.Layout;
using SkiaSharp;
using Stylet;
@ -42,14 +43,16 @@ namespace Artemis.UI.Screens.Settings.Device
Device = device;
PanZoomViewModel = new PanZoomViewModel();
SelectedLeds = new BindableCollection<ArtemisLed>();
Items.Add(factory.DevicePropertiesTabViewModel(device));
if (device.RgbDevice.DeviceInfo.DeviceType == RGBDeviceType.Keyboard)
Items.Add(factory.InputMappingsTabViewModel(device));
Items.Add(factory.DeviceInfoTabViewModel(device));
Items.Add(factory.DeviceLedsTabViewModel(device));
ActiveItem = Items.First();
DisplayName = $"{device.RgbDevice.DeviceInfo.Model} | Artemis";
SelectedLeds = new BindableCollection<ArtemisLed>();
}
public ArtemisDevice Device { get; }

View File

@ -0,0 +1,83 @@
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.Screens.Settings.Device.Tabs"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:Shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" x:Class="Artemis.UI.Screens.Settings.Device.Tabs.InputMappingsTabView"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance {x:Type local:InputMappingsTabViewModel}}">
<UserControl.Resources>
<ResourceDictionary>
<Shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
<Style x:Key="CenteredTextColumn" TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</ResourceDictionary>
</UserControl.Resources>
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0">
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}">
<Run Text="Introduction" />
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}" TextWrapping="Wrap">
In some cases you may want Artemis to map key presses to different LEDs.
This is useful when your logical layout swaps keys around (like Hungarian layouts where the Z and Y keys are swapped).<LineBreak /><LineBreak />
In this tab you can set up these custom input mappings, simply click on a LED and press a key, Artemis will from then on consider that LED pressed whenever you press the same key again.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="1" Margin="0 20" Visibility="{Binding SelectedLed, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted}">
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}" TextAlignment="Center">Select a LED in the preview on the left side to get started...</TextBlock>
</StackPanel>
<StackPanel Grid.Row="1" Margin="0 20" Visibility="{Binding SelectedLed, Converter={StaticResource NullToVisibilityConverter}}">
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}" TextAlignment="Center">
<Run Text="Current target LED: " /><Run Text="{Binding SelectedLed.RgbLed.Id, Mode=OneWay}" />
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}" TextAlignment="Center">
<Run Text="Press the key you want to remap" /><Run Text=" " /><Run FontWeight="Bold" Text="{Binding SelectedLed.RgbLed.Id, Mode=OneWay}" /><Run Text=" " /><Run Text="to..." />
</TextBlock>
</StackPanel>
<DataGrid Grid.Row="2"
ItemsSource="{Binding InputMappings}"
CanUserSortColumns="True"
IsReadOnly="True"
CanUserAddRows="False"
AutoGenerateColumns="False"
CanUserResizeRows="False"
materialDesign:DataGridAssist.CellPadding="8"
materialDesign:DataGridAssist.ColumnHeaderPadding="8"
VirtualizingPanel.VirtualizationMode="Standard"
Margin="10">
<DataGrid.Columns>
<materialDesign:DataGridTextColumn Binding="{Binding Item1.RgbLed.Id}" ElementStyle="{StaticResource CenteredTextColumn}" Header="Original LED ID" Width="*" />
<materialDesign:DataGridTextColumn Binding="{Binding Item2.RgbLed.Id}" ElementStyle="{StaticResource CenteredTextColumn}" Header="Remapped LED ID" Width="*" />
<DataGridTemplateColumn Width="45" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Command="{s:Action DeleteMapping}"
CommandParameter="{Binding}"
Style="{StaticResource MaterialDesignIconForegroundButton}"
ToolTip="Delete mapping"
Width="25"
Height="25">
<materialDesign:PackIcon Kind="Delete" Width="18" Height="18" />
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</UserControl>

View File

@ -0,0 +1,103 @@
using System;
using System.Collections.Specialized;
using System.Linq;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Exceptions;
using RGB.NET.Core;
using Stylet;
namespace Artemis.UI.Screens.Settings.Device.Tabs
{
public class InputMappingsTabViewModel : Screen
{
private readonly IRgbService _rgbService;
private readonly IInputService _inputService;
private ArtemisLed _selectedLed;
public InputMappingsTabViewModel(ArtemisDevice device, IRgbService rgbService, IInputService inputService)
{
if (device.RgbDevice.DeviceInfo.DeviceType != RGBDeviceType.Keyboard)
throw new ArtemisUIException("The input mappings tab only supports keyboards");
_rgbService = rgbService;
_inputService = inputService;
Device = device;
DisplayName = "INPUT MAPPINGS";
InputMappings = new BindableCollection<Tuple<ArtemisLed, ArtemisLed>>();
}
public ArtemisDevice Device { get; }
public ArtemisLed SelectedLed
{
get => _selectedLed;
set => SetAndNotify(ref _selectedLed, value);
}
public BindableCollection<Tuple<ArtemisLed, ArtemisLed>> InputMappings { get; }
public void DeleteMapping(Tuple<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);
((DeviceDialogViewModel) Parent).SelectedLeds.Clear();
UpdateInputMappings();
}
private void UpdateInputMappings()
{
if (InputMappings.Any())
InputMappings.Clear();
InputMappings.AddRange(Device.InputMappings.Select(m => new Tuple<ArtemisLed, ArtemisLed>(m.Key, m.Value)));
}
private void SelectedLedsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
SelectedLed = ((DeviceDialogViewModel) Parent).SelectedLeds.FirstOrDefault();
}
#region Overrides of Screen
/// <inheritdoc />
protected override void OnActivate()
{
UpdateInputMappings();
_inputService.KeyboardKeyUp += InputServiceOnKeyboardKeyUp;
((DeviceDialogViewModel) Parent).SelectedLeds.CollectionChanged += SelectedLedsOnCollectionChanged;
base.OnActivate();
}
/// <inheritdoc />
protected override void OnDeactivate()
{
InputMappings.Clear();
_inputService.KeyboardKeyUp -= InputServiceOnKeyboardKeyUp;
((DeviceDialogViewModel) Parent).SelectedLeds.CollectionChanged -= SelectedLedsOnCollectionChanged;
base.OnDeactivate();
}
#endregion
}
}

View File

@ -24,21 +24,28 @@ namespace Artemis.UI.Screens.Settings.Tabs.Devices
_settingsVmFactory = settingsVmFactory;
}
protected override void OnInitialActivate()
#region Overrides of AllActive
/// <inheritdoc />
protected override void OnActivate()
{
// Take it off the UI thread to avoid freezing on tab change
Task.Run(async () =>
{
if (Items.Any())
Items.Clear();
await Task.Delay(200);
List<DeviceSettingsViewModel> instances = _rgbService.Devices.Select(d => _settingsVmFactory.CreateDeviceSettingsViewModel(d)).ToList();
foreach (DeviceSettingsViewModel deviceSettingsViewModel in instances)
Items.Add(deviceSettingsViewModel);
});
base.OnInitialActivate();
base.OnActivate();
}
#endregion
public async Task<bool> ShowDeviceDisableDialog()
{
if (_confirmedDisable)

View File

@ -72,17 +72,17 @@
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Startup delay</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" TextWrapping="Wrap">
Set the amount of seconds to wait before running Artemis with Windows. <LineBreak/>
Set the amount of seconds to wait before running Artemis with Windows. <LineBreak />
If some devices don't work because Artemis starts before the manufacturer's software, try increasing this value.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<TextBox Style="{StaticResource MaterialDesignFilledTextBox}"
Text="{Binding AutoRunDelay}"
Text="{Binding AutoRunDelay}"
IsEnabled="{Binding StartWithWindows}"
Width="100"
Width="100"
materialDesign:TextFieldAssist.SuffixText="sec"
materialDesign:HintAssist.IsFloating="false"/>
materialDesign:HintAssist.IsFloating="false" />
</StackPanel>
</Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="-15 5" />
@ -103,13 +103,13 @@
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ComboBox Style="{StaticResource MaterialDesignFilledComboBox}"
<ComboBox Style="{StaticResource MaterialDesignFilledComboBox}"
Width="100"
SelectedValue="{Binding SelectedColorScheme}"
ItemsSource="{Binding ColorSchemes}"
SelectedValuePath="Value"
DisplayMemberPath="Description"
materialDesign:HintAssist.IsFloating="false"/>
SelectedValue="{Binding SelectedColorScheme}"
ItemsSource="{Binding ColorSchemes}"
SelectedValuePath="Value"
DisplayMemberPath="Description"
materialDesign:HintAssist.IsFloating="false" />
</StackPanel>
</Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="-15 5" />
@ -130,13 +130,13 @@
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ComboBox Style="{StaticResource MaterialDesignFilledComboBox}"
<ComboBox Style="{StaticResource MaterialDesignFilledComboBox}"
Width="100"
SelectedValue="{Binding SelectedLogLevel}"
ItemsSource="{Binding LogLevels}"
SelectedValuePath="Value"
SelectedValuePath="Value"
DisplayMemberPath="Description"
materialDesign:HintAssist.IsFloating="false"/>
materialDesign:HintAssist.IsFloating="false" />
</StackPanel>
</Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="-15 5" />
@ -187,7 +187,7 @@
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<TextBox Style="{StaticResource MaterialDesignFilledTextBox}" Text="{Binding WebServerPortSetting.Value}" Width="100"
materialDesign:HintAssist.IsFloating="false"/>
materialDesign:HintAssist.IsFloating="false" />
</StackPanel>
</Grid>
</StackPanel>
@ -343,7 +343,7 @@
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ComboBox Style="{StaticResource MaterialDesignFilledComboBox}"
Width="100"
SelectedItem="{Binding PreferredGraphicsContext}"
SelectedItem="{Binding PreferredGraphicsContext}"
materialDesign:HintAssist.IsFloating="false">
<system:String>Software</system:String>
<system:String>Vulkan</system:String>
@ -364,16 +364,17 @@
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Render scale</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" TextWrapping="Wrap">
Sets the resolution Artemis renders at, higher scale means more CPU-usage, especially on large surfaces.
Sets the resolution Artemis renders at, higher scale means more CPU-usage, especially on large surfaces. <LineBreak />
<Run Foreground="GoldenRod">A scale of 25% may be required for very large surfaces but could cause LED dimming or bleeding.</Run>
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ComboBox Style="{StaticResource MaterialDesignFilledComboBox}"
Width="100"
SelectedItem="{Binding SelectedRenderScale}"
ItemsSource="{Binding RenderScales}"
SelectedItem="{Binding SelectedRenderScale}"
ItemsSource="{Binding RenderScales}"
DisplayMemberPath="Item1"
materialDesign:HintAssist.IsFloating="false"/>
materialDesign:HintAssist.IsFloating="false" />
</StackPanel>
</Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="-15 5" />
@ -399,7 +400,7 @@
SelectedItem="{Binding SelectedTargetFrameRate}"
ItemsSource="{Binding TargetFrameRates}"
DisplayMemberPath="Item1"
materialDesign:HintAssist.IsFloating="false"/>
materialDesign:HintAssist.IsFloating="false" />
</StackPanel>
</Grid>
</StackPanel>