diff --git a/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs b/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs index 6b8152b9d..895c76cd7 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 @@ -13,5 +15,6 @@ namespace Ryujinx.Common.Configuration.Hid public Key VolumeDown { get; set; } public Key CustomVSyncIntervalIncrement { get; set; } public Key CustomVSyncIntervalDecrement { get; set; } + public List CycleControllers { get; set; } } } diff --git a/src/Ryujinx/AppHost.cs b/src/Ryujinx/AppHost.cs index 5872b278f..64bdb82ff 100644 --- a/src/Ryujinx/AppHost.cs +++ b/src/Ryujinx/AppHost.cs @@ -34,6 +34,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; @@ -48,6 +49,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; @@ -1297,6 +1299,18 @@ 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: + var player = currentHotkeyState - KeyboardHotkeyState.CycleControllersPlayer1; + var ivm = new UI.ViewModels.Input.InputViewModel(); + Dispatcher.UIThread.Invoke(() => ivm.CyclePlayerDevice(player)); + break; case KeyboardHotkeyState.None: (_keyboardInterface as AvaloniaKeyboard).Clear(); break; @@ -1379,6 +1393,15 @@ namespace Ryujinx.Ava state = KeyboardHotkeyState.CustomVSyncIntervalDecrement; } + 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.json b/src/Ryujinx/Assets/locales.json index 33dd61d42..5fbed5b54 100644 --- a/src/Ryujinx/Assets/locales.json +++ b/src/Ryujinx/Assets/locales.json @@ -4917,6 +4917,30 @@ "zh_TW": "啟用警告日誌" } }, + { + "ID": "SettingsTabHotkeysCycleControllers", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "Cycle Controllers", + "es_ES": "", + "fr_FR": "", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": "", + "no_NO": "", + "pl_PL": "", + "pt_BR": "", + "ru_RU": "", + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": "", + "zh_TW": "" + } + }, { "ID": "SettingsTabLoggingEnableErrorLogs", "Translations": { @@ -21574,4 +21598,4 @@ } } ] -} \ No newline at end of file +} diff --git a/src/Ryujinx/Common/KeyboardHotkeyState.cs b/src/Ryujinx/Common/KeyboardHotkeyState.cs index 060c678d2..de2042a23 100644 --- a/src/Ryujinx/Common/KeyboardHotkeyState.cs +++ b/src/Ryujinx/Common/KeyboardHotkeyState.cs @@ -14,5 +14,13 @@ namespace Ryujinx.Ava.Common VolumeDown, CustomVSyncIntervalIncrement, CustomVSyncIntervalDecrement, + 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 4c7a6bd02..24564a8fa 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 { @@ -126,8 +131,15 @@ 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) { ToggleVSyncMode = config.ToggleVSyncMode; @@ -141,7 +153,9 @@ namespace Ryujinx.Ava.UI.Models.Input VolumeDown = config.VolumeDown; CustomVSyncIntervalIncrement = config.CustomVSyncIntervalIncrement; CustomVSyncIntervalDecrement = config.CustomVSyncIntervalDecrement; + CycleControllers.AddRange((config.CycleControllers ?? []).Select((x, i) => new CycleController(i + 1, x))); } + CycleControllers.CollectionChanged += (sender, e) => OnPropertyChanged(nameof(CanRemoveCycleController)); } public KeyboardHotkeys GetConfig() @@ -159,6 +173,7 @@ namespace Ryujinx.Ava.UI.Models.Input VolumeDown = VolumeDown, CustomVSyncIntervalIncrement = CustomVSyncIntervalIncrement, CustomVSyncIntervalDecrement = CustomVSyncIntervalDecrement, + 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 000000000..a8cad0ab6 --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/CycleController.cs @@ -0,0 +1,48 @@ +using Ryujinx.Ava.Common.Locale; +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 = v switch + { + 1 => LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer1], + 2 => LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer2], + 3 => LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer3], + 4 => LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer4], + 5 => LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer5], + 6 => LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer6], + 7 => LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer7], + 8 => LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer8], + _ => LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer] + " " + v + }; + Hotkey = x; + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs index 493e6659d..fd70f10ee 100644 --- a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs @@ -266,6 +266,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)>(); @@ -754,38 +758,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() @@ -899,5 +899,13 @@ namespace Ryujinx.Ava.UI.ViewModels.Input AvaloniaKeyboardDriver.Dispose(); } + + public void CyclePlayerDevice(int player) + { + LoadDevices(); + PlayerId = (PlayerIndex)player; + Device = (Device + 1) % Devices.Count; + Save(); + } } } diff --git a/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml index da0957e02..2fbc5c167 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml +++ b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml @@ -1,4 +1,4 @@ - - - -