diff --git a/README.md b/README.md index 064e062dd..c66d69d0c 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs index 298e824f3..34a3be646 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs @@ -9,6 +9,7 @@ RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, new FloatDataBindingConverter(), "End"); CurrentValueSet += OnCurrentValueSet; + DefaultValue = new FloatRange(); } /// @@ -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(); } } } \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs index 474fa33e1..0b1942356 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs @@ -9,6 +9,7 @@ RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, new IntDataBindingConverter(), "End"); CurrentValueSet += OnCurrentValueSet; + DefaultValue = new IntRange(); } /// @@ -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(); } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/FloatRange.cs b/src/Artemis.Core/Models/Profile/LayerProperties/FloatRange.cs index 3df43ae4d..2e696d7af 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/FloatRange.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/FloatRange.cs @@ -9,6 +9,14 @@ namespace Artemis.Core { private readonly Random _rand; + /// + /// Creates a new instance of the class + /// + public FloatRange() + { + _rand = new Random(); + } + /// /// Creates a new instance of the class /// diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/IntRange.cs b/src/Artemis.Core/Models/Profile/LayerProperties/IntRange.cs index ebe6a4d80..e93e6a349 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/IntRange.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/IntRange.cs @@ -9,6 +9,14 @@ namespace Artemis.Core { private readonly Random _rand; + /// + /// Creates a new instance of the class + /// + public IntRange() + { + _rand = new Random(); + } + /// /// Creates a new instance of the class /// diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs index afe35710d..e5733c8e2 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs @@ -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(); + else + _baseValue = default!; + _keyframes = new List>(); } diff --git a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs index 0baf7d834..a8ae3a17d 100644 --- a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs +++ b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs @@ -35,10 +35,9 @@ namespace Artemis.Core IsEnabled = true; InputIdentifiers = new List(); + InputMappings = new Dictionary(); - Leds = rgbDevice.Select(l => new ArtemisLed(l, this)).ToList().AsReadOnly(); - LedIds = new ReadOnlyDictionary(Leds.ToDictionary(l => l.RgbLed.Id, l => l)); - + UpdateLeds(); ApplyKeyboardLayout(); ApplyToEntity(); CalculateRenderProperties(); @@ -52,12 +51,12 @@ namespace Artemis.Core DeviceProvider = deviceProvider; InputIdentifiers = new List(); + InputMappings = new Dictionary(); + 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(Leds.ToDictionary(l => l.RgbLed.Id, l => l)); - + UpdateLeds(); ApplyKeyboardLayout(); } @@ -110,6 +109,8 @@ namespace Artemis.Core /// public List InputIdentifiers { get; } + public Dictionary InputMappings { get; } + /// /// Gets or sets the X-position of the device /// @@ -287,20 +288,27 @@ namespace Artemis.Core /// Attempts to retrieve the that corresponds the provided RGB.NET /// /// The RGB.NET to find the corresponding for + /// If , LEDs mapped to different LEDs are taken into consideration /// If found, the corresponding ; otherwise . - public ArtemisLed? GetLed(Led led) + public ArtemisLed? GetLed(Led led, bool applyInputMapping) { - return GetLed(led.Id); + return GetLed(led.Id, applyInputMapping); } /// /// Attempts to retrieve the that corresponds the provided RGB.NET /// /// The RGB.NET to find the corresponding for + /// If , LEDs mapped to different LEDs are taken into consideration /// If found, the corresponding ; otherwise . - 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(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(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() diff --git a/src/Artemis.Core/Models/Surface/ArtemisDeviceInputIdentifier.cs b/src/Artemis.Core/Models/Surface/ArtemisDeviceInputIdentifier.cs index 6af52fccb..3eab18dff 100644 --- a/src/Artemis.Core/Models/Surface/ArtemisDeviceInputIdentifier.cs +++ b/src/Artemis.Core/Models/Surface/ArtemisDeviceInputIdentifier.cs @@ -1,4 +1,5 @@ using Artemis.Core.Services; +using RGB.NET.Core; namespace Artemis.Core { @@ -12,7 +13,7 @@ namespace Artemis.Core /// /// The full type and namespace of the this identifier is used by /// A value used to identify the device - public ArtemisDeviceInputIdentifier(string inputProvider, object identifier) + internal ArtemisDeviceInputIdentifier(string inputProvider, object identifier) { InputProvider = inputProvider; Identifier = identifier; @@ -28,4 +29,16 @@ namespace Artemis.Core /// 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; + } + } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Input/InputKeyLedIdMap.cs b/src/Artemis.Core/Services/Input/InputKeyLedIdMap.cs index ad67b1a92..f2881c804 100644 --- a/src/Artemis.Core/Services/Input/InputKeyLedIdMap.cs +++ b/src/Artemis.Core/Services/Input/InputKeyLedIdMap.cs @@ -3,9 +3,15 @@ using RGB.NET.Core; namespace Artemis.Core.Services { - internal static class InputKeyUtilities + /// + /// Utilities for mapping keys and buttons to LEDs + /// + public static class InputKeyUtilities { - internal static readonly Dictionary KeyboardKeyLedIdMap = new() + /// + /// A dictionary of mappings between and + /// + public static readonly Dictionary 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 MouseButtonLedIdMap = new() + /// + /// A dictionary of mappings between and + /// + public static readonly Dictionary MouseButtonLedIdMap = new() { {MouseButton.Left, LedId.Mouse1}, {MouseButton.Middle, LedId.Mouse2}, diff --git a/src/Artemis.Core/Services/Input/InputService.cs b/src/Artemis.Core/Services/Input/InputService.cs index 6ca8aa735..bed2d47de 100644 --- a/src/Artemis.Core/Services/Input/InputService.cs +++ b/src/Artemis.Core/Services/Input/InputService.cs @@ -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); diff --git a/src/Artemis.Storage/Entities/Surface/DeviceEntity.cs b/src/Artemis.Storage/Entities/Surface/DeviceEntity.cs index 258902d5e..bc1fb4e82 100644 --- a/src/Artemis.Storage/Entities/Surface/DeviceEntity.cs +++ b/src/Artemis.Storage/Entities/Surface/DeviceEntity.cs @@ -7,6 +7,7 @@ namespace Artemis.Storage.Entities.Surface public DeviceEntity() { InputIdentifiers = new List(); + InputMappings = new List(); } public string Id { get; set; } @@ -25,9 +26,16 @@ namespace Artemis.Storage.Entities.Surface public string CustomLayoutPath { get; set; } public List InputIdentifiers { get; set; } + public List InputMappings { get; set; } } + public class InputMappingEntity + { + public int OriginalLedId { get; set; } + public int MappedLedId { get; set; } + } + public class DeviceInputIdentifierEntity { public string InputProvider { get; set; } diff --git a/src/Artemis.UI.Shared/Plugins/LayerBrushes/LayerBrushConfigurationDialog.cs b/src/Artemis.UI.Shared/Plugins/LayerBrushes/LayerBrushConfigurationDialog.cs index e792dbe75..daf80ed91 100644 --- a/src/Artemis.UI.Shared/Plugins/LayerBrushes/LayerBrushConfigurationDialog.cs +++ b/src/Artemis.UI.Shared/Plugins/LayerBrushes/LayerBrushConfigurationDialog.cs @@ -6,6 +6,18 @@ namespace Artemis.UI.Shared.LayerBrushes /// public class LayerBrushConfigurationDialog : LayerBrushConfigurationDialog where T : BrushConfigurationViewModel { + /// + public LayerBrushConfigurationDialog() + { + } + + /// + public LayerBrushConfigurationDialog(int dialogWidth, int dialogHeight) + { + DialogWidth = dialogWidth; + DialogHeight = dialogHeight; + } + /// public override Type Type => typeof(T); } @@ -15,6 +27,16 @@ namespace Artemis.UI.Shared.LayerBrushes /// public abstract class LayerBrushConfigurationDialog : ILayerBrushConfigurationDialog { + /// + /// The default width of the dialog + /// + public int DialogWidth { get; set; } = 800; + + /// + /// The default height of the dialog + /// + public int DialogHeight { get; set; } = 800; + /// /// The type of view model the tab contains /// diff --git a/src/Artemis.UI.Shared/Plugins/LayerEffects/LayerEffectConfigurationDialog.cs b/src/Artemis.UI.Shared/Plugins/LayerEffects/LayerEffectConfigurationDialog.cs index 81bcdb2af..bb043fa93 100644 --- a/src/Artemis.UI.Shared/Plugins/LayerEffects/LayerEffectConfigurationDialog.cs +++ b/src/Artemis.UI.Shared/Plugins/LayerEffects/LayerEffectConfigurationDialog.cs @@ -6,6 +6,19 @@ namespace Artemis.UI.Shared.LayerEffects /// public class LayerEffectConfigurationDialog : LayerEffectConfigurationDialog where T : EffectConfigurationViewModel { + + /// + public LayerEffectConfigurationDialog() + { + } + + /// + public LayerEffectConfigurationDialog(int dialogWidth, int dialogHeight) + { + DialogWidth = dialogWidth; + DialogHeight = dialogHeight; + } + /// public override Type Type => typeof(T); } @@ -15,11 +28,15 @@ namespace Artemis.UI.Shared.LayerEffects /// public abstract class LayerEffectConfigurationDialog : ILayerEffectConfigurationDialog { - // TODO: See if this is still in use /// - /// The layer effect this dialog belongs to + /// The default width of the dialog /// - public BaseLayerEffect? LayerEffect { get; set; } + public int DialogWidth { get; set; } = 800; + + /// + /// The default height of the dialog + /// + public int DialogHeight { get; set; } = 800; /// /// The type of view model the tab contains diff --git a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs index 02d543a4c..e24134dce 100644 --- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -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 diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeGroupViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeGroupViewModel.cs index 30d8fd822..580071f43 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeGroupViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeGroupViewModel.cs @@ -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) { diff --git a/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerBrushSettingsWindowView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerBrushSettingsWindowView.xaml index d0b42c891..25256ee61 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerBrushSettingsWindowView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerBrushSettingsWindowView.xaml @@ -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}" diff --git a/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerBrushSettingsWindowViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerBrushSettingsWindowViewModel.cs index 682be6025..afeab8cb0 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerBrushSettingsWindowViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerBrushSettingsWindowViewModel.cs @@ -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 { - 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 + + /// + 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; diff --git a/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerEffectSettingsWindowView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerEffectSettingsWindowView.xaml index ca3cdda83..a7cb413e4 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerEffectSettingsWindowView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerEffectSettingsWindowView.xaml @@ -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" diff --git a/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerEffectSettingsWindowViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerEffectSettingsWindowViewModel.cs index 6d2438bdf..a1a8a4049 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerEffectSettingsWindowViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerEffectSettingsWindowViewModel.cs @@ -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 { - 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 + + /// + 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; diff --git a/src/Artemis.UI/Screens/Settings/Device/DeviceDialogView.xaml b/src/Artemis.UI/Screens/Settings/Device/DeviceDialogView.xaml index 8134150e7..fe4ab6371 100644 --- a/src/Artemis.UI/Screens/Settings/Device/DeviceDialogView.xaml +++ b/src/Artemis.UI/Screens/Settings/Device/DeviceDialogView.xaml @@ -21,6 +21,9 @@ d:DesignHeight="800" d:DesignWidth="800" d:DataContext="{d:DesignInstance device:DeviceDialogViewModel}" Icon="/Resources/Images/Logo/logo-512.png"> + + + @@ -111,6 +114,14 @@ VerticalAlignment="Center" ShowColors="True" LedClicked="{s:Action OnLedClicked}"/> + + + Device layout by + diff --git a/src/Artemis.UI/Screens/Settings/Device/DeviceDialogViewModel.cs b/src/Artemis.UI/Screens/Settings/Device/DeviceDialogViewModel.cs index be35288c6..62ec00072 100644 --- a/src/Artemis.UI/Screens/Settings/Device/DeviceDialogViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Device/DeviceDialogViewModel.cs @@ -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(); 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(); } public ArtemisDevice Device { get; } diff --git a/src/Artemis.UI/Screens/Settings/Device/Tabs/InputMappingsTabView.xaml b/src/Artemis.UI/Screens/Settings/Device/Tabs/InputMappingsTabView.xaml new file mode 100644 index 000000000..d662eb770 --- /dev/null +++ b/src/Artemis.UI/Screens/Settings/Device/Tabs/InputMappingsTabView.xaml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + 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). + 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. + + + + + Select a LED in the preview on the left side to get started... + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Device/Tabs/InputMappingsTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Device/Tabs/InputMappingsTabViewModel.cs new file mode 100644 index 000000000..9ffbe769b --- /dev/null +++ b/src/Artemis.UI/Screens/Settings/Device/Tabs/InputMappingsTabViewModel.cs @@ -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>(); + } + + public ArtemisDevice Device { get; } + + public ArtemisLed SelectedLed + { + get => _selectedLed; + set => SetAndNotify(ref _selectedLed, value); + } + + public BindableCollection> InputMappings { get; } + + public void DeleteMapping(Tuple 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(m.Key, m.Value))); + } + + private void SelectedLedsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + SelectedLed = ((DeviceDialogViewModel) Parent).SelectedLeds.FirstOrDefault(); + } + + #region Overrides of Screen + + /// + protected override void OnActivate() + { + UpdateInputMappings(); + _inputService.KeyboardKeyUp += InputServiceOnKeyboardKeyUp; + ((DeviceDialogViewModel) Parent).SelectedLeds.CollectionChanged += SelectedLedsOnCollectionChanged; + + base.OnActivate(); + } + + /// + protected override void OnDeactivate() + { + InputMappings.Clear(); + _inputService.KeyboardKeyUp -= InputServiceOnKeyboardKeyUp; + ((DeviceDialogViewModel) Parent).SelectedLeds.CollectionChanged -= SelectedLedsOnCollectionChanged; + base.OnDeactivate(); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsTabViewModel.cs index e3cb174a6..ee9c2f793 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsTabViewModel.cs @@ -24,21 +24,28 @@ namespace Artemis.UI.Screens.Settings.Tabs.Devices _settingsVmFactory = settingsVmFactory; } - protected override void OnInitialActivate() + #region Overrides of AllActive + + /// + 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 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 ShowDeviceDisableDialog() { if (_confirmedDisable) diff --git a/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabView.xaml b/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabView.xaml index 4401bf050..7153e747c 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabView.xaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabView.xaml @@ -72,17 +72,17 @@ Startup delay - Set the amount of seconds to wait before running Artemis with Windows. + Set the amount of seconds to wait before running Artemis with Windows. If some devices don't work because Artemis starts before the manufacturer's software, try increasing this value. + materialDesign:HintAssist.IsFloating="false" /> @@ -103,13 +103,13 @@ - + SelectedValue="{Binding SelectedColorScheme}" + ItemsSource="{Binding ColorSchemes}" + SelectedValuePath="Value" + DisplayMemberPath="Description" + materialDesign:HintAssist.IsFloating="false" /> @@ -130,13 +130,13 @@ - + materialDesign:HintAssist.IsFloating="false" /> @@ -187,7 +187,7 @@ + materialDesign:HintAssist.IsFloating="false" /> @@ -343,7 +343,7 @@ Software Vulkan @@ -364,16 +364,17 @@ Render scale - 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. + A scale of 25% may be required for very large surfaces but could cause LED dimming or bleeding. + materialDesign:HintAssist.IsFloating="false" /> @@ -399,7 +400,7 @@ SelectedItem="{Binding SelectedTargetFrameRate}" ItemsSource="{Binding TargetFrameRates}" DisplayMemberPath="Item1" - materialDesign:HintAssist.IsFloating="false"/> + materialDesign:HintAssist.IsFloating="false" />