Extended hotkeys to player1-8 + h, localized the overlay

This commit is contained in:
Barış Hamil 2025-06-21 01:34:50 +03:00 committed by GreemDev
parent ef0dac5533
commit 3ec079855d
16 changed files with 338 additions and 79 deletions

View file

@ -24373,12 +24373,12 @@
} }
}, },
{ {
"ID": "SettingsTabHotkeysCycleInputDevicePlayer1", "ID": "SettingsTabHotkeysCycleInputDevicePlayerX",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Cycle Input Device Player 1:", "en_US": "Cycle Input Device {0}:",
"es_ES": "", "es_ES": "",
"fr_FR": "", "fr_FR": "",
"he_IL": "", "he_IL": "",
@ -24398,12 +24398,12 @@
} }
}, },
{ {
"ID": "SettingsTabHotkeysCycleInputDevicePlayer2", "ID": "ControllerOverlayTitle",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Cycle Input Device Player 2:", "en_US": "Controller Bindings",
"es_ES": "", "es_ES": "",
"fr_FR": "", "fr_FR": "",
"he_IL": "", "he_IL": "",
@ -24423,12 +24423,12 @@
} }
}, },
{ {
"ID": "SettingsTabHotkeysCycleInputDevicePlayer3", "ID": "ControllerOverlayNoController",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Cycle Input Device Player 3:", "en_US": "No controller assigned",
"es_ES": "", "es_ES": "",
"fr_FR": "", "fr_FR": "",
"he_IL": "", "he_IL": "",
@ -24448,12 +24448,62 @@
} }
}, },
{ {
"ID": "SettingsTabHotkeysCycleInputDevicePlayer4", "ID": "ControllerOverlayKeyboard",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "", "de_DE": "",
"el_GR": "", "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": "", "es_ES": "",
"fr_FR": "", "fr_FR": "",
"he_IL": "", "he_IL": "",

View file

@ -19,5 +19,10 @@ namespace Ryujinx.Common.Configuration.Hid
public Key CycleInputDevicePlayer2 { get; set; } public Key CycleInputDevicePlayer2 { get; set; }
public Key CycleInputDevicePlayer3 { get; set; } public Key CycleInputDevicePlayer3 { get; set; }
public Key CycleInputDevicePlayer4 { 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; }
} }
} }

View file

@ -8,6 +8,18 @@ using OriginalInputBackendType = Ryujinx.Common.Configuration.Hid.InputBackendTy
namespace Ryujinx.Graphics.Gpu.Overlay namespace Ryujinx.Graphics.Gpu.Overlay
{ {
/// <summary>
/// Localization strings for the controller overlay
/// </summary>
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";
}
/// <summary> /// <summary>
/// Controller overlay that shows controller bindings matching the original AXAML design /// Controller overlay that shows controller bindings matching the original AXAML design
/// </summary> /// </summary>
@ -23,9 +35,11 @@ namespace Ryujinx.Graphics.Gpu.Overlay
private const float PlayerTextSize = 22; private const float PlayerTextSize = 22;
private float _lifespan = 0f; private float _lifespan = 0f;
private ControllerOverlayLocalization _localization;
public ControllerOverlay() : base("ControllerOverlay") public ControllerOverlay(ControllerOverlayLocalization localization) : base("ControllerOverlay")
{ {
_localization = localization;
CreateBaseElements(); CreateBaseElements();
} }
@ -42,8 +56,8 @@ namespace Ryujinx.Graphics.Gpu.Overlay
}; };
AddElement(background); AddElement(background);
// Title text // Title text (will be updated with localized text)
var titleText = new TextElement(Padding + 30, Padding, "Controller Bindings", TitleTextSize, SKColors.White) var titleText = new TextElement(Padding + 30, Padding, _localization.TitleText, TitleTextSize, SKColors.White)
{ {
Name = "TitleText", Name = "TitleText",
FontStyle = SKFontStyle.Bold FontStyle = SKFontStyle.Bold
@ -52,21 +66,27 @@ namespace Ryujinx.Graphics.Gpu.Overlay
} }
/// <summary> /// <summary>
/// Show controller bindings matching the original AXAML implementation /// Show controller bindings with localized strings
/// </summary> /// </summary>
public void ShowControllerBindings(List<OriginalInputConfig> inputConfigs, int durationSeconds = 3) public void ShowControllerBindings(List<OriginalInputConfig> inputConfigs, int durationSeconds = 3)
{ {
// Update title text
var titleElement = FindElement<TextElement>("TitleText");
if (titleElement != null)
{
titleElement.Text = _localization.TitleText;
}
// Reset lifespan and opacity // Reset lifespan and opacity
_lifespan = durationSeconds; _lifespan = durationSeconds;
// Clear existing player bindings // Clear existing player bindings
ClearPlayerBindings(); ClearPlayerBindings();
// Debug: Log input data // Group controllers by player index (support all players + handheld)
// Group controllers by player index
var playerBindings = new Dictionary<OriginalPlayerIndex, List<OriginalInputConfig>>(); var playerBindings = new Dictionary<OriginalPlayerIndex, List<OriginalInputConfig>>();
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)) if (!playerBindings.ContainsKey(config.PlayerIndex))
{ {
@ -77,10 +97,17 @@ namespace Ryujinx.Graphics.Gpu.Overlay
float currentY = Padding + 40; // After title float currentY = Padding + 40; // After title
// Add player bindings to UI // Add player bindings to UI (support 8 players + handheld)
for (int i = 0; i < 4; i++) 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)); float rowY = currentY + (i * (PlayerRowHeight + PlayerSpacing));
// Player number with colored background (circular badge) // Player number with colored background (circular badge)
@ -93,13 +120,14 @@ namespace Ryujinx.Graphics.Gpu.Overlay
AddElement(playerBadge); AddElement(playerBadge);
// Player number text // 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}", Name = $"PlayerLabel_{i}",
FontStyle = SKFontStyle.Bold, FontStyle = SKFontStyle.Bold,
TextAlign = SKTextAlign.Center TextAlign = SKTextAlign.Center
}; };
AddElement(playerLabel); AddElement(playerLabelElement);
// Controller info // Controller info
if (playerBindings.ContainsKey(playerIndex)) if (playerBindings.ContainsKey(playerIndex))
@ -107,37 +135,32 @@ namespace Ryujinx.Graphics.Gpu.Overlay
var controllers = playerBindings[playerIndex]; var controllers = playerBindings[playerIndex];
var controllerNames = controllers.Select(c => GetControllerDisplayName(c)).ToList(); 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}", Name = $"ControllerText_{i}",
FontStyle = SKFontStyle.Bold FontStyle = SKFontStyle.Bold
}; };
AddElement(controllerText); AddElement(controllerTextElement);
} }
else 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}", Name = $"NoControllerText_{i}",
FontStyle = SKFontStyle.Italic FontStyle = SKFontStyle.Italic
}; };
AddElement(noControllerText); AddElement(noControllerTextElement);
} }
} }
// Calculate total height and update background // 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<RectangleElement>("Background"); var background = FindElement<RectangleElement>("Background");
if (background != null) if (background != null)
{ {
background.Height = totalHeight; 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) // Show the overlay (position will be set by Window class with actual dimensions)
IsVisible = true; IsVisible = true;
} }
@ -150,19 +173,24 @@ namespace Ryujinx.Graphics.Gpu.Overlay
1 => new SKColor(54, 162, 235), // Blue for Player 2 1 => new SKColor(54, 162, 235), // Blue for Player 2
2 => new SKColor(255, 206, 84), // Yellow for Player 3 2 => new SKColor(255, 206, 84), // Yellow for Player 3
3 => new SKColor(75, 192, 192), // Green for Player 4 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 _ => new SKColor(128, 128, 128) // Gray fallback
}; };
} }
private static string GetControllerDisplayName(OriginalInputConfig config) private string GetControllerDisplayName(OriginalInputConfig config)
{ {
if (string.IsNullOrEmpty(config.Name)) if (string.IsNullOrEmpty(config.Name))
{ {
return config.Backend switch return config.Backend switch
{ {
OriginalInputBackendType.WindowKeyboard => "Keyboard", OriginalInputBackendType.WindowKeyboard => _localization.KeyboardText,
OriginalInputBackendType.GamepadSDL2 => "Controller", OriginalInputBackendType.GamepadSDL2 => _localization.ControllerText,
_ => "Unknown" _ => _localization.UnknownText
}; };
} }

View file

@ -8,9 +8,9 @@ namespace Ryujinx.Graphics.Gpu.Overlay
/// </summary> /// </summary>
public class ImageElement : OverlayElement public class ImageElement : OverlayElement
{ {
private SKBitmap? _bitmap; private SKBitmap _bitmap;
private byte[]? _imageData; private byte[] _imageData;
private string? _imagePath; private string _imagePath;
public SKFilterQuality FilterQuality { get; set; } = SKFilterQuality.Medium; public SKFilterQuality FilterQuality { get; set; } = SKFilterQuality.Medium;
public bool MaintainAspectRatio { get; set; } = true; public bool MaintainAspectRatio { get; set; } = true;

View file

@ -107,9 +107,6 @@ namespace Ryujinx.Graphics.Gpu
_overlayManager = new OverlayManager(); _overlayManager = new OverlayManager();
_frameQueue = new ConcurrentQueue<PresentationTexture>(); _frameQueue = new ConcurrentQueue<PresentationTexture>();
// Initialize controller overlay
InitializeControllerOverlay();
} }
/// <summary> /// <summary>
@ -258,12 +255,11 @@ namespace Ryujinx.Graphics.Gpu
} }
/// <summary> /// <summary>
/// Initialize controller overlay /// Add overlay to the overlay manager
/// </summary> /// </summary>
private void InitializeControllerOverlay() public void AddOverlay(Overlay.Overlay overlay)
{ {
var controllerOverlay = new ControllerOverlay(); _overlayManager.AddOverlay(overlay);
_overlayManager.AddOverlay(controllerOverlay);
} }
/// <summary> /// <summary>
@ -363,17 +359,6 @@ namespace Ryujinx.Graphics.Gpu
return false; return false;
} }
/// <summary>
/// Show controller overlay with the provided input configurations
/// </summary>
/// <param name="inputConfigs">List of input configurations to display</param>
/// <param name="durationSeconds">Duration to show the overlay in seconds</param>
public void ShowControllerBindings(List<Common.Configuration.Hid.InputConfig> inputConfigs, int durationSeconds = 3)
{
var controllerOverlay = _overlayManager.FindOverlay("ControllerOverlay") as ControllerOverlay;
controllerOverlay?.ShowControllerBindings(inputConfigs, durationSeconds);
}
/// <summary> /// <summary>
/// Get the overlay manager for external access /// Get the overlay manager for external access
/// </summary> /// </summary>

View file

@ -19,5 +19,10 @@ namespace Ryujinx.Ava.Common
CycleInputDevicePlayer2, CycleInputDevicePlayer2,
CycleInputDevicePlayer3, CycleInputDevicePlayer3,
CycleInputDevicePlayer4, CycleInputDevicePlayer4,
CycleInputDevicePlayer5,
CycleInputDevicePlayer6,
CycleInputDevicePlayer7,
CycleInputDevicePlayer8,
CycleInputDeviceHandheld,
} }
} }

View file

@ -1,3 +1,4 @@
using Avalonia.Data.Converters;
using Avalonia.Markup.Xaml.MarkupExtensions; using Avalonia.Markup.Xaml.MarkupExtensions;
using Projektanker.Icons.Avalonia; using Projektanker.Icons.Avalonia;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
@ -18,11 +19,19 @@ namespace Ryujinx.Ava.Common.Markup
internal class LocaleExtension(LocaleKeys key) : BasicMarkupExtension<string> internal class LocaleExtension(LocaleKeys key) : BasicMarkupExtension<string>
{ {
public IValueConverter Converter { get; set; }
public override string Name => "Translation"; public override string Name => "Translation";
protected override string Value => LocaleManager.Instance[key]; protected override string Value => LocaleManager.Instance[key];
protected override void ConfigureBindingExtension(CompiledBindingExtension bindingExtension) 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<string> internal class WindowTitleExtension(LocaleKeys key, bool includeVersion) : BasicMarkupExtension<string>

View file

@ -33,6 +33,7 @@ using Ryujinx.Common.Utilities;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.GAL.Multithreading; using Ryujinx.Graphics.GAL.Multithreading;
using Ryujinx.Graphics.Gpu; using Ryujinx.Graphics.Gpu;
using Ryujinx.Graphics.Gpu.Overlay;
using Ryujinx.Graphics.OpenGL; using Ryujinx.Graphics.OpenGL;
using Ryujinx.Graphics.Vulkan; using Ryujinx.Graphics.Vulkan;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
@ -129,6 +130,7 @@ namespace Ryujinx.Ava.Systems
private readonly bool _isFirmwareTitle; private readonly bool _isFirmwareTitle;
private readonly Lock _lockObject = new(); private readonly Lock _lockObject = new();
private ControllerOverlay _controllerOverlay;
public event EventHandler AppExit; public event EventHandler AppExit;
public event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent; public event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent;
@ -932,6 +934,18 @@ namespace Ryujinx.Ava.Systems
_viewModel.UiHandler _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() private static IHardwareDeviceDriver InitializeAudio()
@ -1350,6 +1364,21 @@ namespace Ryujinx.Ava.Systems
case KeyboardHotkeyState.CycleInputDevicePlayer4: case KeyboardHotkeyState.CycleInputDevicePlayer4:
CycleInputDevice(HLE.HOS.Services.Hid.PlayerIndex.Player4); CycleInputDevice(HLE.HOS.Services.Hid.PlayerIndex.Player4);
break; 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: case KeyboardHotkeyState.None:
(_keyboardInterface as AvaloniaKeyboard).Clear(); (_keyboardInterface as AvaloniaKeyboard).Clear();
break; break;
@ -1458,7 +1487,7 @@ namespace Ryujinx.Ava.Systems
NpadManager.ReloadConfiguration(currentConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse); NpadManager.ReloadConfiguration(currentConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);
// Show controller overlay // Show controller overlay
ShowControllerOverlay(currentConfig); ShowControllerOverlay(currentConfig, ConfigurationState.Instance.ControllerOverlayInputCycleDuration.Value);
} }
private InputConfig CreateDefaultInputConfig((DeviceType Type, string Id, string Name) device, PlayerIndex playerIndex) private InputConfig CreateDefaultInputConfig((DeviceType Type, string Id, string Name) device, PlayerIndex playerIndex)
@ -1476,21 +1505,17 @@ namespace Ryujinx.Ava.Systems
return null; return null;
} }
public void ShowControllerOverlay(List<InputConfig> inputConfigs, int duration)
private void ShowControllerOverlay(List<InputConfig> inputConfigs)
{ {
try try
{ {
// Show overlay through the GPU context window directly if (_controllerOverlay != null)
if (Device?.Gpu?.Window != null)
{ {
int duration = ConfigurationState.Instance.ControllerOverlayInputCycleDuration.Value; _controllerOverlay.ShowControllerBindings(inputConfigs, duration);
Device.Gpu.Window.ShowControllerBindings(inputConfigs, duration);
} }
else 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) catch (Exception ex)
@ -1567,6 +1592,26 @@ namespace Ryujinx.Ava.Systems
{ {
state = KeyboardHotkeyState.CycleInputDevicePlayer4; 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; return state;
} }

View file

@ -15,7 +15,7 @@ namespace Ryujinx.Ava.Systems.Configuration
/// <summary> /// <summary>
/// The current version of the file format /// The current version of the file format
/// </summary> /// </summary>
public const int CurrentVersion = 71; public const int CurrentVersion = 72;
/// <summary> /// <summary>
/// Version of the configuration file format /// Version of the configuration file format

View file

@ -486,6 +486,34 @@ namespace Ryujinx.Ava.Systems.Configuration
{ {
cff.ControllerOverlayGameStartDuration = 3; cff.ControllerOverlayGameStartDuration = 3;
cff.ControllerOverlayInputCycleDuration = 2; 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
};
}) })
); );
} }

View file

@ -273,7 +273,16 @@ namespace Ryujinx.Ava.Systems.Configuration
CustomVSyncIntervalIncrement = Key.Unbound, CustomVSyncIntervalIncrement = Key.Unbound,
CustomVSyncIntervalDecrement = Key.Unbound, CustomVSyncIntervalDecrement = Key.Unbound,
TurboMode = 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.RainbowSpeed.Value = 1f;
Hid.InputConfig.Value = Hid.InputConfig.Value =

View file

@ -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();
}
}
}

View file

@ -40,6 +40,16 @@ namespace Ryujinx.Ava.UI.Models.Input
[ObservableProperty] private Key _cycleInputDevicePlayer4; [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) public HotkeyConfig(KeyboardHotkeys config)
{ {
if (config == null) if (config == null)
@ -62,6 +72,11 @@ namespace Ryujinx.Ava.UI.Models.Input
CycleInputDevicePlayer2 = config.CycleInputDevicePlayer2; CycleInputDevicePlayer2 = config.CycleInputDevicePlayer2;
CycleInputDevicePlayer3 = config.CycleInputDevicePlayer3; CycleInputDevicePlayer3 = config.CycleInputDevicePlayer3;
CycleInputDevicePlayer4 = config.CycleInputDevicePlayer4; CycleInputDevicePlayer4 = config.CycleInputDevicePlayer4;
CycleInputDevicePlayer5 = config.CycleInputDevicePlayer5;
CycleInputDevicePlayer6 = config.CycleInputDevicePlayer6;
CycleInputDevicePlayer7 = config.CycleInputDevicePlayer7;
CycleInputDevicePlayer8 = config.CycleInputDevicePlayer8;
CycleInputDeviceHandheld = config.CycleInputDeviceHandheld;
} }
public KeyboardHotkeys GetConfig() => public KeyboardHotkeys GetConfig() =>
@ -83,7 +98,12 @@ namespace Ryujinx.Ava.UI.Models.Input
CycleInputDevicePlayer1 = CycleInputDevicePlayer1, CycleInputDevicePlayer1 = CycleInputDevicePlayer1,
CycleInputDevicePlayer2 = CycleInputDevicePlayer2, CycleInputDevicePlayer2 = CycleInputDevicePlayer2,
CycleInputDevicePlayer3 = CycleInputDevicePlayer3, CycleInputDevicePlayer3 = CycleInputDevicePlayer3,
CycleInputDevicePlayer4 = CycleInputDevicePlayer4 CycleInputDevicePlayer4 = CycleInputDevicePlayer4,
CycleInputDevicePlayer5 = CycleInputDevicePlayer5,
CycleInputDevicePlayer6 = CycleInputDevicePlayer6,
CycleInputDevicePlayer7 = CycleInputDevicePlayer7,
CycleInputDevicePlayer8 = CycleInputDevicePlayer8,
CycleInputDeviceHandheld = CycleInputDeviceHandheld
}; };
} }
} }

View file

@ -1600,9 +1600,9 @@ namespace Ryujinx.Ava.UI.ViewModels
// Code where conditions will be executed after loading user configuration // Code where conditions will be executed after loading user configuration
if (ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() != backendThreadingInit) if (ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() != backendThreadingInit)
{ {
Rebooter.RebootAppWithGame(application.Path, Rebooter.RebootAppWithGame(application.Path,
[ [
"--bt", "--bt",
ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() 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 // Always show overlay - if no configs, it will show test data
int duration = ConfigurationState.Instance.ControllerOverlayGameStartDuration.Value; int duration = ConfigurationState.Instance.ControllerOverlayGameStartDuration.Value;
// Show overlay through the GPU context window directly // Show overlay through the GPU context window directly
if (AppHost?.Device?.Gpu?.Window != null) AppHost.ShowControllerOverlay(inputConfigs, duration);
{
AppHost.Device.Gpu.Window.ShowControllerBindings(inputConfigs, duration);
}
} }
catch (Exception ex) catch (Exception ex)
{ {

View file

@ -121,29 +121,59 @@
<CheckBox IsChecked="{Binding KeyboardHotkey.TurboModeWhileHeld}" /> <CheckBox IsChecked="{Binding KeyboardHotkey.TurboModeWhileHeld}" />
</StackPanel> </StackPanel>
<StackPanel> <StackPanel>
<TextBlock Text="{ext:Locale SettingsTabHotkeysCycleInputDevicePlayer1}" Classes="settingHeader" /> <TextBlock Text="{ext:Locale ControllerSettingsPlayer1, Converter={x:Static helpers:PlayerHotkeyLabelConverter.Instance}}" Classes="settingHeader" />
<ToggleButton Name="CycleInputDevicePlayer1"> <ToggleButton Name="CycleInputDevicePlayer1">
<TextBlock Text="{Binding KeyboardHotkey.CycleInputDevicePlayer1, Converter={x:Static helpers:KeyValueConverter.Instance}}" /> <TextBlock Text="{Binding KeyboardHotkey.CycleInputDevicePlayer1, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
<StackPanel> <StackPanel>
<TextBlock Text="{ext:Locale SettingsTabHotkeysCycleInputDevicePlayer2}" Classes="settingHeader" /> <TextBlock Text="{ext:Locale ControllerSettingsPlayer2, Converter={x:Static helpers:PlayerHotkeyLabelConverter.Instance}}" Classes="settingHeader" />
<ToggleButton Name="CycleInputDevicePlayer2"> <ToggleButton Name="CycleInputDevicePlayer2">
<TextBlock Text="{Binding KeyboardHotkey.CycleInputDevicePlayer2, Converter={x:Static helpers:KeyValueConverter.Instance}}" /> <TextBlock Text="{Binding KeyboardHotkey.CycleInputDevicePlayer2, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
<StackPanel> <StackPanel>
<TextBlock Text="{ext:Locale SettingsTabHotkeysCycleInputDevicePlayer3}" Classes="settingHeader" /> <TextBlock Text="{ext:Locale ControllerSettingsPlayer3, Converter={x:Static helpers:PlayerHotkeyLabelConverter.Instance}}" Classes="settingHeader" />
<ToggleButton Name="CycleInputDevicePlayer3"> <ToggleButton Name="CycleInputDevicePlayer3">
<TextBlock Text="{Binding KeyboardHotkey.CycleInputDevicePlayer3, Converter={x:Static helpers:KeyValueConverter.Instance}}" /> <TextBlock Text="{Binding KeyboardHotkey.CycleInputDevicePlayer3, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
<StackPanel> <StackPanel>
<TextBlock Text="{ext:Locale SettingsTabHotkeysCycleInputDevicePlayer4}" Classes="settingHeader" /> <TextBlock Text="{ext:Locale ControllerSettingsPlayer4, Converter={x:Static helpers:PlayerHotkeyLabelConverter.Instance}}" Classes="settingHeader" />
<ToggleButton Name="CycleInputDevicePlayer4"> <ToggleButton Name="CycleInputDevicePlayer4">
<TextBlock Text="{Binding KeyboardHotkey.CycleInputDevicePlayer4, Converter={x:Static helpers:KeyValueConverter.Instance}}" /> <TextBlock Text="{Binding KeyboardHotkey.CycleInputDevicePlayer4, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
<StackPanel>
<TextBlock Text="{ext:Locale ControllerSettingsPlayer5, Converter={x:Static helpers:PlayerHotkeyLabelConverter.Instance}}" Classes="settingHeader" />
<ToggleButton Name="CycleInputDevicePlayer5">
<TextBlock Text="{Binding KeyboardHotkey.CycleInputDevicePlayer5, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel>
<TextBlock Text="{ext:Locale ControllerSettingsPlayer6, Converter={x:Static helpers:PlayerHotkeyLabelConverter.Instance}}" Classes="settingHeader" />
<ToggleButton Name="CycleInputDevicePlayer6">
<TextBlock Text="{Binding KeyboardHotkey.CycleInputDevicePlayer6, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel>
<TextBlock Text="{ext:Locale ControllerSettingsPlayer7, Converter={x:Static helpers:PlayerHotkeyLabelConverter.Instance}}" Classes="settingHeader" />
<ToggleButton Name="CycleInputDevicePlayer7">
<TextBlock Text="{Binding KeyboardHotkey.CycleInputDevicePlayer7, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel>
<TextBlock Text="{ext:Locale ControllerSettingsPlayer8, Converter={x:Static helpers:PlayerHotkeyLabelConverter.Instance}}" Classes="settingHeader" />
<ToggleButton Name="CycleInputDevicePlayer8">
<TextBlock Text="{Binding KeyboardHotkey.CycleInputDevicePlayer8, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel>
<TextBlock Text="{ext:Locale ControllerSettingsHandheld, Converter={x:Static helpers:PlayerHotkeyLabelConverter.Instance}}" Classes="settingHeader" />
<ToggleButton Name="CycleInputDeviceHandheld">
<TextBlock Text="{Binding KeyboardHotkey.CycleInputDeviceHandheld, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
</StackPanel> </StackPanel>
</Border> </Border>
</ScrollViewer> </ScrollViewer>

View file

@ -86,7 +86,12 @@ namespace Ryujinx.Ava.UI.Views.Settings
{ "CycleInputDevicePlayer1", () => viewModel.KeyboardHotkey.CycleInputDevicePlayer1 = Key.Unbound }, { "CycleInputDevicePlayer1", () => viewModel.KeyboardHotkey.CycleInputDevicePlayer1 = Key.Unbound },
{ "CycleInputDevicePlayer2", () => viewModel.KeyboardHotkey.CycleInputDevicePlayer2 = Key.Unbound }, { "CycleInputDevicePlayer2", () => viewModel.KeyboardHotkey.CycleInputDevicePlayer2 = Key.Unbound },
{ "CycleInputDevicePlayer3", () => viewModel.KeyboardHotkey.CycleInputDevicePlayer3 = 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)) if (buttonActions.TryGetValue(_currentAssigner.ToggledButton.Name, out Action action))
@ -178,6 +183,21 @@ namespace Ryujinx.Ava.UI.Views.Settings
case "CycleInputDevicePlayer4": case "CycleInputDevicePlayer4":
ViewModel.KeyboardHotkey.CycleInputDevicePlayer4 = buttonValue.AsHidType<Key>(); ViewModel.KeyboardHotkey.CycleInputDevicePlayer4 = buttonValue.AsHidType<Key>();
break; break;
case "CycleInputDevicePlayer5":
ViewModel.KeyboardHotkey.CycleInputDevicePlayer5 = buttonValue.AsHidType<Key>();
break;
case "CycleInputDevicePlayer6":
ViewModel.KeyboardHotkey.CycleInputDevicePlayer6 = buttonValue.AsHidType<Key>();
break;
case "CycleInputDevicePlayer7":
ViewModel.KeyboardHotkey.CycleInputDevicePlayer7 = buttonValue.AsHidType<Key>();
break;
case "CycleInputDevicePlayer8":
ViewModel.KeyboardHotkey.CycleInputDevicePlayer8 = buttonValue.AsHidType<Key>();
break;
case "CycleInputDeviceHandheld":
ViewModel.KeyboardHotkey.CycleInputDeviceHandheld = buttonValue.AsHidType<Key>();
break;
} }
}); });
} }