From 3ec079855d116eaed930143636ef83db648da053 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Hamil?= Date: Sat, 21 Jun 2025 01:34:50 +0300 Subject: [PATCH] Extended hotkeys to player1-8 + h, localized the overlay --- assets/locales.json | 66 +++++++++++++-- .../Configuration/Hid/KeyboardHotkeys.cs | 5 ++ .../Overlay/ControllerOverlay.cs | 80 +++++++++++++------ .../Overlay/ImageElement.cs | 6 +- src/Ryujinx.Graphics.Gpu/Window.cs | 21 +---- src/Ryujinx/Common/KeyboardHotkeyState.cs | 5 ++ src/Ryujinx/Common/Markup/MarkupExtensions.cs | 11 ++- src/Ryujinx/Systems/AppHost.cs | 63 ++++++++++++--- .../Configuration/ConfigurationFileFormat.cs | 2 +- .../ConfigurationState.Migration.cs | 28 +++++++ .../Configuration/ConfigurationState.cs | 11 ++- .../Converters/PlayerHotkeyLabelConverter.cs | 28 +++++++ src/Ryujinx/UI/Models/Input/HotkeyConfig.cs | 22 ++++- .../UI/ViewModels/MainWindowViewModel.cs | 9 +-- .../Views/Settings/SettingsHotkeysView.axaml | 38 ++++++++- .../Settings/SettingsHotkeysView.axaml.cs | 22 ++++- 16 files changed, 338 insertions(+), 79 deletions(-) create mode 100644 src/Ryujinx/UI/Helpers/Converters/PlayerHotkeyLabelConverter.cs diff --git a/assets/locales.json b/assets/locales.json index 27c92ccad..533bd5cc3 100644 --- a/assets/locales.json +++ b/assets/locales.json @@ -24373,12 +24373,12 @@ } }, { - "ID": "SettingsTabHotkeysCycleInputDevicePlayer1", + "ID": "SettingsTabHotkeysCycleInputDevicePlayerX", "Translations": { "ar_SA": "", "de_DE": "", "el_GR": "", - "en_US": "Cycle Input Device Player 1:", + "en_US": "Cycle Input Device {0}:", "es_ES": "", "fr_FR": "", "he_IL": "", @@ -24398,12 +24398,12 @@ } }, { - "ID": "SettingsTabHotkeysCycleInputDevicePlayer2", + "ID": "ControllerOverlayTitle", "Translations": { "ar_SA": "", "de_DE": "", "el_GR": "", - "en_US": "Cycle Input Device Player 2:", + "en_US": "Controller Bindings", "es_ES": "", "fr_FR": "", "he_IL": "", @@ -24423,12 +24423,12 @@ } }, { - "ID": "SettingsTabHotkeysCycleInputDevicePlayer3", + "ID": "ControllerOverlayNoController", "Translations": { "ar_SA": "", "de_DE": "", "el_GR": "", - "en_US": "Cycle Input Device Player 3:", + "en_US": "No controller assigned", "es_ES": "", "fr_FR": "", "he_IL": "", @@ -24448,12 +24448,62 @@ } }, { - "ID": "SettingsTabHotkeysCycleInputDevicePlayer4", + "ID": "ControllerOverlayKeyboard", "Translations": { "ar_SA": "", "de_DE": "", "el_GR": "", - "en_US": "Cycle Input Device Player 4:", + "en_US": "Keyboard", + "es_ES": "", + "fr_FR": "", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": "", + "no_NO": "", + "pl_PL": "", + "pt_BR": "", + "ru_RU": "", + "sv_SE": "", + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": "", + "zh_TW": "" + } + }, + { + "ID": "ControllerOverlayController", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "Controller", + "es_ES": "", + "fr_FR": "", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": "", + "no_NO": "", + "pl_PL": "", + "pt_BR": "", + "ru_RU": "", + "sv_SE": "", + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": "", + "zh_TW": "" + } + }, + { + "ID": "ControllerOverlayUnknown", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "Unknown", "es_ES": "", "fr_FR": "", "he_IL": "", diff --git a/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs b/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs index f84434ba1..cc6ec55d5 100644 --- a/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs +++ b/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs @@ -19,5 +19,10 @@ namespace Ryujinx.Common.Configuration.Hid public Key CycleInputDevicePlayer2 { get; set; } public Key CycleInputDevicePlayer3 { get; set; } public Key CycleInputDevicePlayer4 { get; set; } + public Key CycleInputDevicePlayer5 { get; set; } + public Key CycleInputDevicePlayer6 { get; set; } + public Key CycleInputDevicePlayer7 { get; set; } + public Key CycleInputDevicePlayer8 { get; set; } + public Key CycleInputDeviceHandheld { get; set; } } } diff --git a/src/Ryujinx.Graphics.Gpu/Overlay/ControllerOverlay.cs b/src/Ryujinx.Graphics.Gpu/Overlay/ControllerOverlay.cs index 69795d48e..d22389ed5 100644 --- a/src/Ryujinx.Graphics.Gpu/Overlay/ControllerOverlay.cs +++ b/src/Ryujinx.Graphics.Gpu/Overlay/ControllerOverlay.cs @@ -8,6 +8,18 @@ using OriginalInputBackendType = Ryujinx.Common.Configuration.Hid.InputBackendTy namespace Ryujinx.Graphics.Gpu.Overlay { + /// + /// Localization strings for the controller overlay + /// + public class ControllerOverlayLocalization + { + public string TitleText { get; set; } = "Controller Bindings"; + public string NoControllerText { get; set; } = "No controller assigned"; + public string KeyboardText { get; set; } = "Keyboard"; + public string ControllerText { get; set; } = "Controller"; + public string UnknownText { get; set; } = "Unknown"; + } + /// /// Controller overlay that shows controller bindings matching the original AXAML design /// @@ -23,9 +35,11 @@ namespace Ryujinx.Graphics.Gpu.Overlay private const float PlayerTextSize = 22; private float _lifespan = 0f; + private ControllerOverlayLocalization _localization; - public ControllerOverlay() : base("ControllerOverlay") + public ControllerOverlay(ControllerOverlayLocalization localization) : base("ControllerOverlay") { + _localization = localization; CreateBaseElements(); } @@ -42,8 +56,8 @@ namespace Ryujinx.Graphics.Gpu.Overlay }; AddElement(background); - // Title text - var titleText = new TextElement(Padding + 30, Padding, "Controller Bindings", TitleTextSize, SKColors.White) + // Title text (will be updated with localized text) + var titleText = new TextElement(Padding + 30, Padding, _localization.TitleText, TitleTextSize, SKColors.White) { Name = "TitleText", FontStyle = SKFontStyle.Bold @@ -52,21 +66,27 @@ namespace Ryujinx.Graphics.Gpu.Overlay } /// - /// Show controller bindings matching the original AXAML implementation + /// Show controller bindings with localized strings /// public void ShowControllerBindings(List inputConfigs, int durationSeconds = 3) { + // Update title text + var titleElement = FindElement("TitleText"); + if (titleElement != null) + { + titleElement.Text = _localization.TitleText; + } + // Reset lifespan and opacity _lifespan = durationSeconds; // Clear existing player bindings ClearPlayerBindings(); - // Debug: Log input data - // Group controllers by player index + // Group controllers by player index (support all players + handheld) var playerBindings = new Dictionary>(); - foreach (var config in inputConfigs.Where(c => c.PlayerIndex <= OriginalPlayerIndex.Player4)) + foreach (var config in inputConfigs.Where(c => c.PlayerIndex <= OriginalPlayerIndex.Handheld)) { if (!playerBindings.ContainsKey(config.PlayerIndex)) { @@ -77,10 +97,17 @@ namespace Ryujinx.Graphics.Gpu.Overlay float currentY = Padding + 40; // After title - // Add player bindings to UI - for (int i = 0; i < 4; i++) + // Add player bindings to UI (support 8 players + handheld) + var playerIndices = new[] + { + OriginalPlayerIndex.Player1, OriginalPlayerIndex.Player2, OriginalPlayerIndex.Player3, OriginalPlayerIndex.Player4, + OriginalPlayerIndex.Player5, OriginalPlayerIndex.Player6, OriginalPlayerIndex.Player7, OriginalPlayerIndex.Player8, + OriginalPlayerIndex.Handheld + }; + + for (int i = 0; i < playerIndices.Length; i++) { - var playerIndex = (OriginalPlayerIndex)i; + var playerIndex = playerIndices[i]; float rowY = currentY + (i * (PlayerRowHeight + PlayerSpacing)); // Player number with colored background (circular badge) @@ -93,13 +120,14 @@ namespace Ryujinx.Graphics.Gpu.Overlay AddElement(playerBadge); // Player number text - var playerLabel = new TextElement(Padding + 12, rowY + 2, $"P{i + 1}", PlayerTextSize, SKColors.White) + string playerLabel = playerIndex == OriginalPlayerIndex.Handheld ? "H" : $"P{(int)playerIndex + 1}"; + var playerLabelElement = new TextElement(Padding + 12, rowY + 2, playerLabel, PlayerTextSize, SKColors.White) { Name = $"PlayerLabel_{i}", FontStyle = SKFontStyle.Bold, TextAlign = SKTextAlign.Center }; - AddElement(playerLabel); + AddElement(playerLabelElement); // Controller info if (playerBindings.ContainsKey(playerIndex)) @@ -107,37 +135,32 @@ namespace Ryujinx.Graphics.Gpu.Overlay var controllers = playerBindings[playerIndex]; var controllerNames = controllers.Select(c => GetControllerDisplayName(c)).ToList(); - var controllerText = new TextElement(Padding + 56, rowY + 2, string.Join(", ", controllerNames), PlayerTextSize, new SKColor(144, 238, 144)) // LightGreen + var controllerTextElement = new TextElement(Padding + 56, rowY + 2, string.Join(", ", controllerNames), PlayerTextSize, new SKColor(144, 238, 144)) // LightGreen { Name = $"ControllerText_{i}", FontStyle = SKFontStyle.Bold }; - AddElement(controllerText); + AddElement(controllerTextElement); } else { - var noControllerText = new TextElement(Padding + 56, rowY + 2, "No controller assigned", PlayerTextSize, new SKColor(128, 128, 128)) // Gray + var noControllerTextElement = new TextElement(Padding + 56, rowY + 2, _localization.NoControllerText, PlayerTextSize, new SKColor(128, 128, 128)) // Gray { Name = $"NoControllerText_{i}", FontStyle = SKFontStyle.Italic }; - AddElement(noControllerText); + AddElement(noControllerTextElement); } } // Calculate total height and update background - float totalHeight = Padding + 40 + (4 * (PlayerRowHeight + PlayerSpacing)) + Padding + 40; // Extra space for duration text + float totalHeight = Padding + 40 + (playerIndices.Length * (PlayerRowHeight + PlayerSpacing)) + Padding + 20; var background = FindElement("Background"); if (background != null) { background.Height = totalHeight; } - // Duration text at bottom - string durationText = durationSeconds == 1 - ? "This overlay will disappear in 1 second" - : $"This overlay will disappear in {durationSeconds} seconds"; - // Show the overlay (position will be set by Window class with actual dimensions) IsVisible = true; } @@ -150,19 +173,24 @@ namespace Ryujinx.Graphics.Gpu.Overlay 1 => new SKColor(54, 162, 235), // Blue for Player 2 2 => new SKColor(255, 206, 84), // Yellow for Player 3 3 => new SKColor(75, 192, 192), // Green for Player 4 + 4 => new SKColor(153, 102, 255), // Purple for Player 5 + 5 => new SKColor(255, 159, 64), // Orange for Player 6 + 6 => new SKColor(199, 199, 199), // Light Gray for Player 7 + 7 => new SKColor(83, 102, 255), // Indigo for Player 8 + 8 => new SKColor(255, 99, 132), // Pink for Handheld _ => new SKColor(128, 128, 128) // Gray fallback }; } - private static string GetControllerDisplayName(OriginalInputConfig config) + private string GetControllerDisplayName(OriginalInputConfig config) { if (string.IsNullOrEmpty(config.Name)) { return config.Backend switch { - OriginalInputBackendType.WindowKeyboard => "Keyboard", - OriginalInputBackendType.GamepadSDL2 => "Controller", - _ => "Unknown" + OriginalInputBackendType.WindowKeyboard => _localization.KeyboardText, + OriginalInputBackendType.GamepadSDL2 => _localization.ControllerText, + _ => _localization.UnknownText }; } diff --git a/src/Ryujinx.Graphics.Gpu/Overlay/ImageElement.cs b/src/Ryujinx.Graphics.Gpu/Overlay/ImageElement.cs index 57122d73d..849a1e5af 100644 --- a/src/Ryujinx.Graphics.Gpu/Overlay/ImageElement.cs +++ b/src/Ryujinx.Graphics.Gpu/Overlay/ImageElement.cs @@ -8,9 +8,9 @@ namespace Ryujinx.Graphics.Gpu.Overlay /// public class ImageElement : OverlayElement { - private SKBitmap? _bitmap; - private byte[]? _imageData; - private string? _imagePath; + private SKBitmap _bitmap; + private byte[] _imageData; + private string _imagePath; public SKFilterQuality FilterQuality { get; set; } = SKFilterQuality.Medium; public bool MaintainAspectRatio { get; set; } = true; diff --git a/src/Ryujinx.Graphics.Gpu/Window.cs b/src/Ryujinx.Graphics.Gpu/Window.cs index 0c59967a0..e07aa29e2 100644 --- a/src/Ryujinx.Graphics.Gpu/Window.cs +++ b/src/Ryujinx.Graphics.Gpu/Window.cs @@ -107,9 +107,6 @@ namespace Ryujinx.Graphics.Gpu _overlayManager = new OverlayManager(); _frameQueue = new ConcurrentQueue(); - - // Initialize controller overlay - InitializeControllerOverlay(); } /// @@ -258,12 +255,11 @@ namespace Ryujinx.Graphics.Gpu } /// - /// Initialize controller overlay + /// Add overlay to the overlay manager /// - private void InitializeControllerOverlay() + public void AddOverlay(Overlay.Overlay overlay) { - var controllerOverlay = new ControllerOverlay(); - _overlayManager.AddOverlay(controllerOverlay); + _overlayManager.AddOverlay(overlay); } /// @@ -363,17 +359,6 @@ namespace Ryujinx.Graphics.Gpu return false; } - /// - /// Show controller overlay with the provided input configurations - /// - /// List of input configurations to display - /// Duration to show the overlay in seconds - public void ShowControllerBindings(List inputConfigs, int durationSeconds = 3) - { - var controllerOverlay = _overlayManager.FindOverlay("ControllerOverlay") as ControllerOverlay; - controllerOverlay?.ShowControllerBindings(inputConfigs, durationSeconds); - } - /// /// Get the overlay manager for external access /// diff --git a/src/Ryujinx/Common/KeyboardHotkeyState.cs b/src/Ryujinx/Common/KeyboardHotkeyState.cs index f7b7406d2..2df1d5970 100644 --- a/src/Ryujinx/Common/KeyboardHotkeyState.cs +++ b/src/Ryujinx/Common/KeyboardHotkeyState.cs @@ -19,5 +19,10 @@ namespace Ryujinx.Ava.Common CycleInputDevicePlayer2, CycleInputDevicePlayer3, CycleInputDevicePlayer4, + CycleInputDevicePlayer5, + CycleInputDevicePlayer6, + CycleInputDevicePlayer7, + CycleInputDevicePlayer8, + CycleInputDeviceHandheld, } } diff --git a/src/Ryujinx/Common/Markup/MarkupExtensions.cs b/src/Ryujinx/Common/Markup/MarkupExtensions.cs index b2ed01517..07d7b3613 100644 --- a/src/Ryujinx/Common/Markup/MarkupExtensions.cs +++ b/src/Ryujinx/Common/Markup/MarkupExtensions.cs @@ -1,3 +1,4 @@ +using Avalonia.Data.Converters; using Avalonia.Markup.Xaml.MarkupExtensions; using Projektanker.Icons.Avalonia; using Ryujinx.Ava.Common.Locale; @@ -18,11 +19,19 @@ namespace Ryujinx.Ava.Common.Markup internal class LocaleExtension(LocaleKeys key) : BasicMarkupExtension { + public IValueConverter Converter { get; set; } + public override string Name => "Translation"; protected override string Value => LocaleManager.Instance[key]; protected override void ConfigureBindingExtension(CompiledBindingExtension bindingExtension) - => bindingExtension.Source = LocaleManager.Instance; + { + bindingExtension.Source = LocaleManager.Instance; + if (Converter != null) + { + bindingExtension.Converter = Converter; + } + } } internal class WindowTitleExtension(LocaleKeys key, bool includeVersion) : BasicMarkupExtension diff --git a/src/Ryujinx/Systems/AppHost.cs b/src/Ryujinx/Systems/AppHost.cs index 6c9cec18a..756771de5 100644 --- a/src/Ryujinx/Systems/AppHost.cs +++ b/src/Ryujinx/Systems/AppHost.cs @@ -33,6 +33,7 @@ using Ryujinx.Common.Utilities; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL.Multithreading; using Ryujinx.Graphics.Gpu; +using Ryujinx.Graphics.Gpu.Overlay; using Ryujinx.Graphics.OpenGL; using Ryujinx.Graphics.Vulkan; using Ryujinx.HLE.FileSystem; @@ -129,6 +130,7 @@ namespace Ryujinx.Ava.Systems private readonly bool _isFirmwareTitle; private readonly Lock _lockObject = new(); + private ControllerOverlay _controllerOverlay; public event EventHandler AppExit; public event EventHandler StatusUpdatedEvent; @@ -932,6 +934,18 @@ namespace Ryujinx.Ava.Systems _viewModel.UiHandler ) ); + + // Initialize controller overlay with localization + var localization = new ControllerOverlayLocalization + { + TitleText = LocaleManager.Instance[LocaleKeys.ControllerOverlayTitle], + NoControllerText = LocaleManager.Instance[LocaleKeys.ControllerOverlayNoController], + KeyboardText = LocaleManager.Instance[LocaleKeys.ControllerOverlayKeyboard], + ControllerText = LocaleManager.Instance[LocaleKeys.ControllerOverlayController], + UnknownText = LocaleManager.Instance[LocaleKeys.ControllerOverlayUnknown] + }; + _controllerOverlay = new ControllerOverlay(localization); + Device.Gpu.Window.AddOverlay(_controllerOverlay); } private static IHardwareDeviceDriver InitializeAudio() @@ -1350,6 +1364,21 @@ namespace Ryujinx.Ava.Systems case KeyboardHotkeyState.CycleInputDevicePlayer4: CycleInputDevice(HLE.HOS.Services.Hid.PlayerIndex.Player4); break; + case KeyboardHotkeyState.CycleInputDevicePlayer5: + CycleInputDevice(HLE.HOS.Services.Hid.PlayerIndex.Player5); + break; + case KeyboardHotkeyState.CycleInputDevicePlayer6: + CycleInputDevice(HLE.HOS.Services.Hid.PlayerIndex.Player6); + break; + case KeyboardHotkeyState.CycleInputDevicePlayer7: + CycleInputDevice(HLE.HOS.Services.Hid.PlayerIndex.Player7); + break; + case KeyboardHotkeyState.CycleInputDevicePlayer8: + CycleInputDevice(HLE.HOS.Services.Hid.PlayerIndex.Player8); + break; + case KeyboardHotkeyState.CycleInputDeviceHandheld: + CycleInputDevice(HLE.HOS.Services.Hid.PlayerIndex.Handheld); + break; case KeyboardHotkeyState.None: (_keyboardInterface as AvaloniaKeyboard).Clear(); break; @@ -1458,7 +1487,7 @@ namespace Ryujinx.Ava.Systems NpadManager.ReloadConfiguration(currentConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse); // Show controller overlay - ShowControllerOverlay(currentConfig); + ShowControllerOverlay(currentConfig, ConfigurationState.Instance.ControllerOverlayInputCycleDuration.Value); } private InputConfig CreateDefaultInputConfig((DeviceType Type, string Id, string Name) device, PlayerIndex playerIndex) @@ -1476,21 +1505,17 @@ namespace Ryujinx.Ava.Systems return null; } - - - private void ShowControllerOverlay(List inputConfigs) + public void ShowControllerOverlay(List inputConfigs, int duration) { try { - // Show overlay through the GPU context window directly - if (Device?.Gpu?.Window != null) + if (_controllerOverlay != null) { - int duration = ConfigurationState.Instance.ControllerOverlayInputCycleDuration.Value; - Device.Gpu.Window.ShowControllerBindings(inputConfigs, duration); + _controllerOverlay.ShowControllerBindings(inputConfigs, duration); } else { - Logger.Warning?.Print(LogClass.Application, "AppHost: Cannot show overlay - Device.Gpu.Window is null"); + Logger.Warning?.Print(LogClass.Application, "AppHost: Cannot show overlay - ControllerOverlay is null"); } } catch (Exception ex) @@ -1567,6 +1592,26 @@ namespace Ryujinx.Ava.Systems { state = KeyboardHotkeyState.CycleInputDevicePlayer4; } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.CycleInputDevicePlayer5)) + { + state = KeyboardHotkeyState.CycleInputDevicePlayer5; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.CycleInputDevicePlayer6)) + { + state = KeyboardHotkeyState.CycleInputDevicePlayer6; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.CycleInputDevicePlayer7)) + { + state = KeyboardHotkeyState.CycleInputDevicePlayer7; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.CycleInputDevicePlayer8)) + { + state = KeyboardHotkeyState.CycleInputDevicePlayer8; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.CycleInputDeviceHandheld)) + { + state = KeyboardHotkeyState.CycleInputDeviceHandheld; + } return state; } diff --git a/src/Ryujinx/Systems/Configuration/ConfigurationFileFormat.cs b/src/Ryujinx/Systems/Configuration/ConfigurationFileFormat.cs index 0f45d04d1..eaa92c92a 100644 --- a/src/Ryujinx/Systems/Configuration/ConfigurationFileFormat.cs +++ b/src/Ryujinx/Systems/Configuration/ConfigurationFileFormat.cs @@ -15,7 +15,7 @@ namespace Ryujinx.Ava.Systems.Configuration /// /// The current version of the file format /// - public const int CurrentVersion = 71; + public const int CurrentVersion = 72; /// /// Version of the configuration file format diff --git a/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs b/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs index 6ad687688..3cf2e04d7 100644 --- a/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs +++ b/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs @@ -486,6 +486,34 @@ namespace Ryujinx.Ava.Systems.Configuration { cff.ControllerOverlayGameStartDuration = 3; cff.ControllerOverlayInputCycleDuration = 2; + }), + (72, static cff => + { + cff.Hotkeys = new KeyboardHotkeys + { + ToggleVSyncMode = cff.Hotkeys.ToggleVSyncMode, + Screenshot = cff.Hotkeys.Screenshot, + ShowUI = cff.Hotkeys.ShowUI, + Pause = cff.Hotkeys.Pause, + ToggleMute = cff.Hotkeys.ToggleMute, + ResScaleUp = cff.Hotkeys.ResScaleUp, + ResScaleDown = cff.Hotkeys.ResScaleDown, + VolumeUp = cff.Hotkeys.VolumeUp, + VolumeDown = cff.Hotkeys.VolumeDown, + CustomVSyncIntervalIncrement = cff.Hotkeys.CustomVSyncIntervalIncrement, + CustomVSyncIntervalDecrement = cff.Hotkeys.CustomVSyncIntervalDecrement, + TurboMode = cff.Hotkeys.TurboMode, + TurboModeWhileHeld = cff.Hotkeys.TurboModeWhileHeld, + CycleInputDevicePlayer1 = Key.Unbound, + CycleInputDevicePlayer2 = Key.Unbound, + CycleInputDevicePlayer3 = Key.Unbound, + CycleInputDevicePlayer4 = Key.Unbound, + CycleInputDevicePlayer5 = Key.Unbound, + CycleInputDevicePlayer6 = Key.Unbound, + CycleInputDevicePlayer7 = Key.Unbound, + CycleInputDevicePlayer8 = Key.Unbound, + CycleInputDeviceHandheld = Key.Unbound + }; }) ); } diff --git a/src/Ryujinx/Systems/Configuration/ConfigurationState.cs b/src/Ryujinx/Systems/Configuration/ConfigurationState.cs index 71f73bc65..0d4211e1b 100644 --- a/src/Ryujinx/Systems/Configuration/ConfigurationState.cs +++ b/src/Ryujinx/Systems/Configuration/ConfigurationState.cs @@ -273,7 +273,16 @@ namespace Ryujinx.Ava.Systems.Configuration CustomVSyncIntervalIncrement = Key.Unbound, CustomVSyncIntervalDecrement = Key.Unbound, TurboMode = Key.Unbound, - TurboModeWhileHeld = false + TurboModeWhileHeld = false, + CycleInputDevicePlayer1 = Key.Unbound, + CycleInputDevicePlayer2 = Key.Unbound, + CycleInputDevicePlayer3 = Key.Unbound, + CycleInputDevicePlayer4 = Key.Unbound, + CycleInputDevicePlayer5 = Key.Unbound, + CycleInputDevicePlayer6 = Key.Unbound, + CycleInputDevicePlayer7 = Key.Unbound, + CycleInputDevicePlayer8 = Key.Unbound, + CycleInputDeviceHandheld = Key.Unbound }; Hid.RainbowSpeed.Value = 1f; Hid.InputConfig.Value = diff --git a/src/Ryujinx/UI/Helpers/Converters/PlayerHotkeyLabelConverter.cs b/src/Ryujinx/UI/Helpers/Converters/PlayerHotkeyLabelConverter.cs new file mode 100644 index 000000000..9ac7392a6 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/Converters/PlayerHotkeyLabelConverter.cs @@ -0,0 +1,28 @@ +using Avalonia.Data.Converters; +using Ryujinx.Ava.Common.Locale; +using System; +using System.Globalization; + +namespace Ryujinx.Ava.UI.Helpers +{ + internal class PlayerHotkeyLabelConverter : IValueConverter + { + public static readonly PlayerHotkeyLabelConverter Instance = new(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is string playerName && !string.IsNullOrEmpty(playerName)) + { + string baseText = LocaleManager.Instance[LocaleKeys.SettingsTabHotkeysCycleInputDevicePlayerX]; + return string.Format(baseText, playerName); + } + + return string.Empty; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + } +} diff --git a/src/Ryujinx/UI/Models/Input/HotkeyConfig.cs b/src/Ryujinx/UI/Models/Input/HotkeyConfig.cs index 78ab40678..b6de26aa0 100644 --- a/src/Ryujinx/UI/Models/Input/HotkeyConfig.cs +++ b/src/Ryujinx/UI/Models/Input/HotkeyConfig.cs @@ -40,6 +40,16 @@ namespace Ryujinx.Ava.UI.Models.Input [ObservableProperty] private Key _cycleInputDevicePlayer4; + [ObservableProperty] private Key _cycleInputDevicePlayer5; + + [ObservableProperty] private Key _cycleInputDevicePlayer6; + + [ObservableProperty] private Key _cycleInputDevicePlayer7; + + [ObservableProperty] private Key _cycleInputDevicePlayer8; + + [ObservableProperty] private Key _cycleInputDeviceHandheld; + public HotkeyConfig(KeyboardHotkeys config) { if (config == null) @@ -62,6 +72,11 @@ namespace Ryujinx.Ava.UI.Models.Input CycleInputDevicePlayer2 = config.CycleInputDevicePlayer2; CycleInputDevicePlayer3 = config.CycleInputDevicePlayer3; CycleInputDevicePlayer4 = config.CycleInputDevicePlayer4; + CycleInputDevicePlayer5 = config.CycleInputDevicePlayer5; + CycleInputDevicePlayer6 = config.CycleInputDevicePlayer6; + CycleInputDevicePlayer7 = config.CycleInputDevicePlayer7; + CycleInputDevicePlayer8 = config.CycleInputDevicePlayer8; + CycleInputDeviceHandheld = config.CycleInputDeviceHandheld; } public KeyboardHotkeys GetConfig() => @@ -83,7 +98,12 @@ namespace Ryujinx.Ava.UI.Models.Input CycleInputDevicePlayer1 = CycleInputDevicePlayer1, CycleInputDevicePlayer2 = CycleInputDevicePlayer2, CycleInputDevicePlayer3 = CycleInputDevicePlayer3, - CycleInputDevicePlayer4 = CycleInputDevicePlayer4 + CycleInputDevicePlayer4 = CycleInputDevicePlayer4, + CycleInputDevicePlayer5 = CycleInputDevicePlayer5, + CycleInputDevicePlayer6 = CycleInputDevicePlayer6, + CycleInputDevicePlayer7 = CycleInputDevicePlayer7, + CycleInputDevicePlayer8 = CycleInputDevicePlayer8, + CycleInputDeviceHandheld = CycleInputDeviceHandheld }; } } diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index 1a8bce7dc..4c73f0aa4 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -1600,9 +1600,9 @@ namespace Ryujinx.Ava.UI.ViewModels // Code where conditions will be executed after loading user configuration if (ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() != backendThreadingInit) { - Rebooter.RebootAppWithGame(application.Path, + Rebooter.RebootAppWithGame(application.Path, [ - "--bt", + "--bt", ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() ]); @@ -1710,10 +1710,7 @@ namespace Ryujinx.Ava.UI.ViewModels // Always show overlay - if no configs, it will show test data int duration = ConfigurationState.Instance.ControllerOverlayGameStartDuration.Value; // Show overlay through the GPU context window directly - if (AppHost?.Device?.Gpu?.Window != null) - { - AppHost.Device.Gpu.Window.ShowControllerBindings(inputConfigs, duration); - } + AppHost.ShowControllerOverlay(inputConfigs, duration); } catch (Exception ex) { diff --git a/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml index ba82812b9..e120a4ef1 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml +++ b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml @@ -121,29 +121,59 @@ - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs index 7f481f82e..e547f2e70 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs +++ b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs @@ -86,7 +86,12 @@ namespace Ryujinx.Ava.UI.Views.Settings { "CycleInputDevicePlayer1", () => viewModel.KeyboardHotkey.CycleInputDevicePlayer1 = Key.Unbound }, { "CycleInputDevicePlayer2", () => viewModel.KeyboardHotkey.CycleInputDevicePlayer2 = Key.Unbound }, { "CycleInputDevicePlayer3", () => viewModel.KeyboardHotkey.CycleInputDevicePlayer3 = Key.Unbound }, - { "CycleInputDevicePlayer4", () => viewModel.KeyboardHotkey.CycleInputDevicePlayer4 = Key.Unbound } + { "CycleInputDevicePlayer4", () => viewModel.KeyboardHotkey.CycleInputDevicePlayer4 = Key.Unbound }, + { "CycleInputDevicePlayer5", () => viewModel.KeyboardHotkey.CycleInputDevicePlayer5 = Key.Unbound }, + { "CycleInputDevicePlayer6", () => viewModel.KeyboardHotkey.CycleInputDevicePlayer6 = Key.Unbound }, + { "CycleInputDevicePlayer7", () => viewModel.KeyboardHotkey.CycleInputDevicePlayer7 = Key.Unbound }, + { "CycleInputDevicePlayer8", () => viewModel.KeyboardHotkey.CycleInputDevicePlayer8 = Key.Unbound }, + { "CycleInputDeviceHandheld", () => viewModel.KeyboardHotkey.CycleInputDeviceHandheld = Key.Unbound } }; if (buttonActions.TryGetValue(_currentAssigner.ToggledButton.Name, out Action action)) @@ -178,6 +183,21 @@ namespace Ryujinx.Ava.UI.Views.Settings case "CycleInputDevicePlayer4": ViewModel.KeyboardHotkey.CycleInputDevicePlayer4 = buttonValue.AsHidType(); break; + case "CycleInputDevicePlayer5": + ViewModel.KeyboardHotkey.CycleInputDevicePlayer5 = buttonValue.AsHidType(); + break; + case "CycleInputDevicePlayer6": + ViewModel.KeyboardHotkey.CycleInputDevicePlayer6 = buttonValue.AsHidType(); + break; + case "CycleInputDevicePlayer7": + ViewModel.KeyboardHotkey.CycleInputDevicePlayer7 = buttonValue.AsHidType(); + break; + case "CycleInputDevicePlayer8": + ViewModel.KeyboardHotkey.CycleInputDevicePlayer8 = buttonValue.AsHidType(); + break; + case "CycleInputDeviceHandheld": + ViewModel.KeyboardHotkey.CycleInputDeviceHandheld = buttonValue.AsHidType(); + break; } }); }