diff --git a/src/Ryujinx.Input.SDL3/GamepadInfo.cs b/src/Ryujinx.Input.SDL3/GamepadInfo.cs deleted file mode 100644 index d0f3d88cf..000000000 --- a/src/Ryujinx.Input.SDL3/GamepadInfo.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Ryujinx.Input.SDL3 -{ - public record GamepadInfo(string driverId, nint gamepadHandle); -} diff --git a/src/Ryujinx.Input.SDL3/SDL3JoyCon.cs b/src/Ryujinx.Input.SDL3/SDL3JoyCon.cs index 8ab87aca0..f6067faff 100644 --- a/src/Ryujinx.Input.SDL3/SDL3JoyCon.cs +++ b/src/Ryujinx.Input.SDL3/SDL3JoyCon.cs @@ -316,6 +316,7 @@ namespace Ryujinx.Input.SDL3 // Do not touch state of button already pressed if (!result.IsPressed(entry.To)) { + result.SetPressed(entry.To, rawState.IsPressed(entry.From)); } } diff --git a/src/Ryujinx.Input.SDL3/SDL3JoyConPair.cs b/src/Ryujinx.Input.SDL3/SDL3JoyConPair.cs index ef8ae9b01..282e50b67 100644 --- a/src/Ryujinx.Input.SDL3/SDL3JoyConPair.cs +++ b/src/Ryujinx.Input.SDL3/SDL3JoyConPair.cs @@ -1,17 +1,31 @@ using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Controller; using System.Collections.Generic; using System.Linq; using System.Numerics; +using System.Threading; using static SDL3.SDL; namespace Ryujinx.Input.SDL3 { - class SDL3JoyConPair(IGamepad left, IGamepad right) : IGamepad + class SDL3JoyConPair(SDL3JoyCon left, SDL3JoyCon right) : IGamepad { + private StandardControllerInputConfig _configuration; + private readonly record struct ButtonMappingEntry(GamepadButtonInputId To, GamepadButtonInputId From) + { + public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not GamepadButtonInputId.Unbound; + } + private readonly StickInputId[] _stickUserMapping = new StickInputId[(int)StickInputId.Count] + { + StickInputId.Unbound, StickInputId.Left, StickInputId.Right, + }; public GamepadFeaturesFlag Features => (left?.Features ?? GamepadFeaturesFlag.None) | (right?.Features ?? GamepadFeaturesFlag.None); public const string Id = "JoyConPair"; + private readonly Lock _userMappingLock = new(); + + private readonly List _buttonsUserMapping = new List(20); string IGamepad.Id => Id; public string Name => "* Nintendo Switch Joy-Con (L/R)"; @@ -25,7 +39,36 @@ namespace Ryujinx.Input.SDL3 public GamepadStateSnapshot GetMappedStateSnapshot() { - return GetStateSnapshot(); + GamepadStateSnapshot rawState = GetStateSnapshot(); + GamepadStateSnapshot result = default; + + lock (_userMappingLock) + { + if (_buttonsUserMapping.Count == 0) + return rawState; + + + // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator + foreach (ButtonMappingEntry entry in _buttonsUserMapping) + { + if (!entry.IsValid) + continue; + + // Do not touch state of button already pressed + if (!result.IsPressed(entry.To)) + { + result.SetPressed(entry.To, rawState.IsPressed(entry.From)); + } + } + + (float leftStickX, float leftStickY) = rawState.GetStick(_stickUserMapping[(int)StickInputId.Left]); + (float rightStickX, float rightStickY) = rawState.GetStick(_stickUserMapping[(int)StickInputId.Right]); + + result.SetStick(StickInputId.Left, leftStickX, leftStickY); + result.SetStick(StickInputId.Right, rightStickX, rightStickY); + } + + return result; } public Vector3 GetMotionData(MotionInputId inputId) @@ -81,14 +124,66 @@ namespace Ryujinx.Input.SDL3 public void SetConfiguration(InputConfig configuration) { - left.SetConfiguration(configuration); - right.SetConfiguration(configuration); + lock (_userMappingLock) + { + _configuration = (StandardControllerInputConfig)configuration; + + _buttonsUserMapping.Clear(); + + // First update sticks + _stickUserMapping[(int)StickInputId.Left] = (StickInputId)_configuration.LeftJoyconStick.Joystick; + _stickUserMapping[(int)StickInputId.Right] = (StickInputId)_configuration.RightJoyconStick.Joystick; + + // Then left joycon + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, + (GamepadButtonInputId)_configuration.LeftJoyconStick.StickButton)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, + (GamepadButtonInputId)_configuration.LeftJoycon.DpadUp)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, + (GamepadButtonInputId)_configuration.LeftJoycon.DpadDown)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, + (GamepadButtonInputId)_configuration.LeftJoycon.DpadLeft)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, + (GamepadButtonInputId)_configuration.LeftJoycon.DpadRight)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, + (GamepadButtonInputId)_configuration.LeftJoycon.ButtonMinus)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, + (GamepadButtonInputId)_configuration.LeftJoycon.ButtonL)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, + (GamepadButtonInputId)_configuration.LeftJoycon.ButtonZl)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, + (GamepadButtonInputId)_configuration.LeftJoycon.ButtonSr)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, + (GamepadButtonInputId)_configuration.LeftJoycon.ButtonSl)); + + // Finally right joycon + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, + (GamepadButtonInputId)_configuration.RightJoyconStick.StickButton)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, + (GamepadButtonInputId)_configuration.RightJoycon.ButtonA)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, + (GamepadButtonInputId)_configuration.RightJoycon.ButtonB)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, + (GamepadButtonInputId)_configuration.RightJoycon.ButtonX)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, + (GamepadButtonInputId)_configuration.RightJoycon.ButtonY)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, + (GamepadButtonInputId)_configuration.RightJoycon.ButtonPlus)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, + (GamepadButtonInputId)_configuration.RightJoycon.ButtonR)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, + (GamepadButtonInputId)_configuration.RightJoycon.ButtonZr)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, + (GamepadButtonInputId)_configuration.RightJoycon.ButtonSr)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, + (GamepadButtonInputId)_configuration.RightJoycon.ButtonSl)); + left.SetConfiguration(configuration); + right.SetConfiguration(configuration); + } } public void SetTriggerThreshold(float triggerThreshold) { - left.SetTriggerThreshold(triggerThreshold); - right.SetTriggerThreshold(triggerThreshold); } public static bool IsCombinable(Dictionary gamepadsInstanceIdsMapping) diff --git a/src/Ryujinx.SDL3-CS/runtimes/win-x64/native/SDL3.dll b/src/Ryujinx.SDL3-CS/runtimes/win-x64/native/SDL3.dll index dcec147d8..9e890e293 100644 Binary files a/src/Ryujinx.SDL3-CS/runtimes/win-x64/native/SDL3.dll and b/src/Ryujinx.SDL3-CS/runtimes/win-x64/native/SDL3.dll differ diff --git a/src/Ryujinx/Assets/Icons/Controller_JoyConPair.svg b/src/Ryujinx/Assets/Icons/Controller_JoyConPair.svg index 8097762df..0a3400dbb 100644 --- a/src/Ryujinx/Assets/Icons/Controller_JoyConPair.svg +++ b/src/Ryujinx/Assets/Icons/Controller_JoyConPair.svg @@ -1,341 +1,252 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs index 02bc17127..d87c18689 100644 --- a/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs @@ -10,6 +10,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input [ObservableProperty] private GamepadInputConfig _config; private bool _isLeft; + public bool IsLeft { get => _isLeft; @@ -22,6 +23,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input } private bool _isRight; + public bool IsRight { get => _isRight; @@ -38,7 +40,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input [ObservableProperty] private SvgImage _image; public readonly InputViewModel ParentModel; - + [ObservableProperty] private string _leftStickPosition; [ObservableProperty] private string _rightStickPosition; @@ -67,5 +69,10 @@ namespace Ryujinx.Ava.UI.ViewModels.Input IsRight = ParentModel.IsRight; Image = ParentModel.Image; } + + public void UpdateImage(string css) + { + Image = new SvgImage { Source = ParentModel.Image.Source, Css = css }; + } } } diff --git a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs index 9bde2adcb..7f5f1aba0 100644 --- a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs @@ -183,7 +183,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input image.Source = source; } - return image; } } diff --git a/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml b/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml index 544b1e766..ff25b3e1a 100644 --- a/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml +++ b/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml @@ -16,6 +16,7 @@ x:CompileBindings="True" mc:Ignorable="d" Focusable="True" + Unloaded="Control_OnUnloaded" > diff --git a/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml.cs b/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml.cs index 3a5907b24..bcf275d1e 100644 --- a/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml.cs +++ b/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml.cs @@ -4,12 +4,15 @@ using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.LogicalTree; +using Avalonia.Threading; using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.ViewModels.Input; using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Input; using Ryujinx.Input.Assigner; using Ryujinx.Input.HLE; +using System; +using System.Text; using System.Threading.Tasks; using StickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId; @@ -247,36 +250,52 @@ namespace Ryujinx.Ava.UI.Views.Input _isRunning = false; } + private string BuildSvgCss(IGamepad gamepad) + { + StringBuilder sb = new StringBuilder(); + for(var i=0;i<(int)GamepadInputId.Count;i++) + { + var button = (GamepadButtonInputId)i; + if (gamepad.GetMappedStateSnapshot().IsPressed(button)) + { + sb.Append($"#{button}{{fill:#00bbdb;}}"); + } + + } + + Console.WriteLine(sb.ToString()); + return sb.ToString(); + } private void StartUpdatingData() { - Task.Run(async () => + Dispatcher.UIThread.InvokeAsync(async () => { while (_isRunning) { - var viewModel = (DataContext as ControllerInputViewModel); + var viewModel = DataContext as ControllerInputViewModel; if (viewModel != null) { IGamepad gamepad = viewModel.ParentModel.SelectedGamepad; - var config = viewModel.Config; + viewModel.UpdateImage(BuildSvgCss(gamepad)); + var config = viewModel.Config; if (config.LeftJoystick != StickInputId.Unbound) { var stickInputId = (Ryujinx.Input.StickInputId)(int)config.LeftJoystick; - (float leftAxisX, float leftAxisY) = - gamepad.GetStick(stickInputId); - viewModel.LeftStickPosition = NpadController.GetJoystickPosition(leftAxisX, leftAxisY, - config.DeadzoneLeft, config.RangeLeft) - .ToString(); + (float leftAxisX, float leftAxisY) = gamepad.GetStick(stickInputId); + var position = NpadController.GetJoystickPosition(leftAxisX, leftAxisY, + config.DeadzoneLeft, config.RangeLeft); + viewModel.LeftStickPosition = $"{position.Dx}, {position.Dy}"; } if (config.RightJoystick != StickInputId.Unbound) { var stickInputId = (Ryujinx.Input.StickInputId)(int)config.RightJoystick; (float rightAxisX, float rightAxisY) = gamepad.GetStick(stickInputId); - viewModel.RightStickPosition = NpadController - .GetJoystickPosition(rightAxisX, rightAxisY, config.DeadzoneRight, config.RangeRight) - .ToString(); + var position = NpadController.GetJoystickPosition(rightAxisX, rightAxisY, + config.DeadzoneRight, config.RangeRight); + viewModel.RightStickPosition = $"{position.Dx}, {position.Dy}"; } }