From 6a555ee9f78caec50aa598fbb5435ea68c4f5f8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Hamil?= Date: Sat, 17 Aug 2024 23:04:01 +0300 Subject: [PATCH] Ability to assign hotkeys to cycle controllers for players (WIP) --- .../Configuration/Hid/KeyboardHotkeys.cs | 3 + src/Ryujinx/AppHost.cs | 29 +++ src/Ryujinx/Assets/Locales/en_US.json | 1 + src/Ryujinx/Common/KeyboardHotkeyState.cs | 8 + src/Ryujinx/UI/Models/Input/HotkeyConfig.cs | 14 ++ src/Ryujinx/UI/ViewModels/CycleController.cs | 36 ++++ .../UI/ViewModels/Input/InputViewModel.cs | 60 +++--- .../Views/Settings/SettingsHotkeysView.axaml | 157 ++++++++++------ .../Settings/SettingsHotkeysView.axaml.cs | 171 ++++++++++-------- 9 files changed, 313 insertions(+), 166 deletions(-) create mode 100644 src/Ryujinx/UI/ViewModels/CycleController.cs diff --git a/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs b/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs index 0cb49ca8ce..2befc34208 100644 --- a/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs +++ b/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; + namespace Ryujinx.Common.Configuration.Hid { public class KeyboardHotkeys @@ -11,5 +13,6 @@ namespace Ryujinx.Common.Configuration.Hid public Key ResScaleDown { get; set; } public Key VolumeUp { get; set; } public Key VolumeDown { get; set; } + public List CycleControllers { get; set; } } } diff --git a/src/Ryujinx/AppHost.cs b/src/Ryujinx/AppHost.cs index 0db8ef4143..100022d07c 100644 --- a/src/Ryujinx/AppHost.cs +++ b/src/Ryujinx/AppHost.cs @@ -32,6 +32,7 @@ using Ryujinx.HLE; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.HLE.HOS.Services.Hid; using Ryujinx.HLE.HOS.SystemState; using Ryujinx.Input; using Ryujinx.Input.HLE; @@ -46,6 +47,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -1200,6 +1202,24 @@ namespace Ryujinx.Ava _viewModel.Volume = Device.GetVolume(); break; + case KeyboardHotkeyState.CycleControllersPlayer1: + case KeyboardHotkeyState.CycleControllersPlayer2: + case KeyboardHotkeyState.CycleControllersPlayer3: + case KeyboardHotkeyState.CycleControllersPlayer4: + case KeyboardHotkeyState.CycleControllersPlayer5: + case KeyboardHotkeyState.CycleControllersPlayer6: + case KeyboardHotkeyState.CycleControllersPlayer7: + case KeyboardHotkeyState.CycleControllersPlayer8: + Dispatcher.UIThread.Invoke(() => { + var player = currentHotkeyState - KeyboardHotkeyState.CycleControllersPlayer1; + var ivm = new UI.ViewModels.Input.InputViewModel(); + ivm.LoadDevices(); + ivm.PlayerId = (Ryujinx.Common.Configuration.Hid.PlayerIndex) player; + ivm.Device = (ivm.Device + 1) % ivm.Devices.Count; + ivm.Save(); + Console.WriteLine($"Cycling controller for player {ivm.PlayerId} to {ivm.Devices[ivm.Device].Name}"); + }); + break; case KeyboardHotkeyState.None: (_keyboardInterface as AvaloniaKeyboard).Clear(); break; @@ -1274,6 +1294,15 @@ namespace Ryujinx.Ava state = KeyboardHotkeyState.VolumeDown; } + foreach (var cycle in ConfigurationState.Instance.Hid.Hotkeys.Value.CycleControllers.Select((value, index) => (value, index))) + { + if (_keyboardInterface.IsPressed((Key)cycle.value)) + { + state = KeyboardHotkeyState.CycleControllersPlayer1 + cycle.index; + break; + } + } + return state; } } diff --git a/src/Ryujinx/Assets/Locales/en_US.json b/src/Ryujinx/Assets/Locales/en_US.json index 74e18056ba..bfbcac01af 100644 --- a/src/Ryujinx/Assets/Locales/en_US.json +++ b/src/Ryujinx/Assets/Locales/en_US.json @@ -738,6 +738,7 @@ "RyujinxUpdaterMessage": "Do you want to update Ryujinx to the latest version?", "SettingsTabHotkeysVolumeUpHotkey": "Increase Volume:", "SettingsTabHotkeysVolumeDownHotkey": "Decrease Volume:", + "SettingsTabHotkeysCycleControllers": "Cycle Controllers", "SettingsEnableMacroHLE": "Enable Macro HLE", "SettingsEnableMacroHLETooltip": "High-level emulation of GPU Macro code.\n\nImproves performance, but may cause graphical glitches in some games.\n\nLeave ON if unsure.", "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", diff --git a/src/Ryujinx/Common/KeyboardHotkeyState.cs b/src/Ryujinx/Common/KeyboardHotkeyState.cs index 6e49209887..7c6e5fae26 100644 --- a/src/Ryujinx/Common/KeyboardHotkeyState.cs +++ b/src/Ryujinx/Common/KeyboardHotkeyState.cs @@ -12,5 +12,13 @@ namespace Ryujinx.Ava.Common ResScaleDown, VolumeUp, VolumeDown, + CycleControllersPlayer1, + CycleControllersPlayer2, + CycleControllersPlayer3, + CycleControllersPlayer4, + CycleControllersPlayer5, + CycleControllersPlayer6, + CycleControllersPlayer7, + CycleControllersPlayer8 } } diff --git a/src/Ryujinx/UI/Models/Input/HotkeyConfig.cs b/src/Ryujinx/UI/Models/Input/HotkeyConfig.cs index b5f53508bd..4c9370aef1 100644 --- a/src/Ryujinx/UI/Models/Input/HotkeyConfig.cs +++ b/src/Ryujinx/UI/Models/Input/HotkeyConfig.cs @@ -1,5 +1,10 @@ +using DynamicData; +using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Common.Configuration.Hid; +using System.Collections.ObjectModel; +using System.Linq; +using System.Windows.Input; namespace Ryujinx.Ava.UI.Models.Input { @@ -104,8 +109,14 @@ namespace Ryujinx.Ava.UI.Models.Input } } + public ObservableCollection CycleControllers { get; set; } = new ObservableCollection(); + public ICommand AddCycleController { get; set; } + public ICommand RemoveCycleController { get; set; } + public bool CanRemoveCycleController => CycleControllers.Count > 0 && CycleControllers.Count < 8; public HotkeyConfig(KeyboardHotkeys config) { + AddCycleController = MiniCommand.Create(() => CycleControllers.Add(new CycleController(CycleControllers.Count + 1, Key.Unbound))); + RemoveCycleController = MiniCommand.Create(() => CycleControllers.Remove(CycleControllers.Last())); if (config != null) { ToggleVsync = config.ToggleVsync; @@ -117,7 +128,9 @@ namespace Ryujinx.Ava.UI.Models.Input ResScaleDown = config.ResScaleDown; VolumeUp = config.VolumeUp; VolumeDown = config.VolumeDown; + CycleControllers.AddRange((config.CycleControllers ?? []).Select((x, i) => new CycleController(i + 1, x))); } + CycleControllers.CollectionChanged += (sender, e) => OnPropertyChanged(nameof(CanRemoveCycleController)); } public KeyboardHotkeys GetConfig() @@ -133,6 +146,7 @@ namespace Ryujinx.Ava.UI.Models.Input ResScaleDown = ResScaleDown, VolumeUp = VolumeUp, VolumeDown = VolumeDown, + CycleControllers = CycleControllers.Select(x => x.Hotkey).ToList() }; return config; diff --git a/src/Ryujinx/UI/ViewModels/CycleController.cs b/src/Ryujinx/UI/ViewModels/CycleController.cs new file mode 100644 index 0000000000..15a1edd114 --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/CycleController.cs @@ -0,0 +1,36 @@ +using Ryujinx.Common.Configuration.Hid; + +namespace Ryujinx.Ava.UI.ViewModels +{ + public class CycleController : BaseModel + { + private string _player; + private Key _hotkey; + + public string Player + { + get => _player; + set + { + _player = value; + OnPropertyChanged(nameof(Player)); + } + } + + public Key Hotkey + { + get => _hotkey; + set + { + _hotkey = value; + OnPropertyChanged(nameof(Hotkey)); + } + } + + public CycleController(int v, Key x) + { + Player = $"Player {v}"; + Hotkey = x; + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs index 89cc6496d5..221e9d0448 100644 --- a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs @@ -256,6 +256,10 @@ namespace Ryujinx.Ava.UI.ViewModels.Input public InputViewModel() { + _mainWindow = + (MainWindow)((IClassicDesktopStyleApplicationLifetime)Application.Current + .ApplicationLifetime).MainWindow; + AvaloniaKeyboardDriver = new AvaloniaKeyboardDriver(_mainWindow); PlayerIndexes = new ObservableCollection(); Controllers = new ObservableCollection(); Devices = new ObservableCollection<(DeviceType Type, string Id, string Name)>(); @@ -740,38 +744,34 @@ namespace Ryujinx.Ava.UI.ViewModels.Input return; } - else + + bool validFileName = ProfileName.IndexOfAny(Path.GetInvalidFileNameChars()) == -1; + + if (!validFileName) { - bool validFileName = ProfileName.IndexOfAny(Path.GetInvalidFileNameChars()) == -1; - - if (validFileName) - { - string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json"); - - InputConfig config = null; - - if (IsKeyboard) - { - config = (ConfigViewModel as KeyboardInputViewModel).Config.GetConfig(); - } - else if (IsController) - { - config = (ConfigViewModel as ControllerInputViewModel).Config.GetConfig(); - } - - config.ControllerType = Controllers[_controller].Type; - - string jsonString = JsonHelper.Serialize(config, _serializerContext.InputConfig); - - await File.WriteAllTextAsync(path, jsonString); - - LoadProfiles(); - } - else - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileInvalidProfileNameErrorMessage]); - } + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileInvalidProfileNameErrorMessage]); + return; } + string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json"); + + InputConfig config = null; + + if (IsKeyboard) + { + config = (ConfigViewModel as KeyboardInputViewModel).Config.GetConfig(); + } + else if (IsController) + { + config = (ConfigViewModel as ControllerInputViewModel).Config.GetConfig(); + } + + config.ControllerType = Controllers[_controller].Type; + + string jsonString = JsonHelper.Serialize(config, _serializerContext.InputConfig); + + await File.WriteAllTextAsync(path, jsonString); + + LoadProfiles(); } public async void RemoveProfile() diff --git a/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml index bffcada055..bd0083b1a9 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml +++ b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml @@ -18,15 +18,15 @@ - - - @@ -42,67 +42,110 @@ VerticalScrollBarVisibility="Auto"> + Spacing="10" + Name="SettingButtons"> - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + +