GamepadButtonAssigner.cs
1 using System; 2 using System.Collections.Generic; 3 using System.IO; 4 using System.Linq; 5 6 namespace Ryujinx.Input.Assigner 7 { 8 /// <summary> 9 /// <see cref="IButtonAssigner"/> implementation for regular <see cref="IGamepad"/>. 10 /// </summary> 11 public class GamepadButtonAssigner : IButtonAssigner 12 { 13 private readonly IGamepad _gamepad; 14 15 private GamepadStateSnapshot _currState; 16 17 private GamepadStateSnapshot _prevState; 18 19 private readonly JoystickButtonDetector _detector; 20 21 private readonly bool _forStick; 22 23 public GamepadButtonAssigner(IGamepad gamepad, float triggerThreshold, bool forStick) 24 { 25 _gamepad = gamepad; 26 _detector = new JoystickButtonDetector(); 27 _forStick = forStick; 28 29 _gamepad?.SetTriggerThreshold(triggerThreshold); 30 } 31 32 public void Initialize() 33 { 34 if (_gamepad != null) 35 { 36 _currState = _gamepad.GetStateSnapshot(); 37 _prevState = _currState; 38 } 39 } 40 41 public void ReadInput() 42 { 43 if (_gamepad != null) 44 { 45 _prevState = _currState; 46 _currState = _gamepad.GetStateSnapshot(); 47 } 48 49 CollectButtonStats(); 50 } 51 52 public bool IsAnyButtonPressed() 53 { 54 return _detector.IsAnyButtonPressed(); 55 } 56 57 public bool ShouldCancel() 58 { 59 return _gamepad == null || !_gamepad.IsConnected; 60 } 61 62 public Button? GetPressedButton() 63 { 64 IEnumerable<GamepadButtonInputId> pressedButtons = _detector.GetPressedButtons(); 65 66 return !_forStick ? new(pressedButtons.FirstOrDefault()) : new((StickInputId)pressedButtons.FirstOrDefault()); 67 } 68 69 private void CollectButtonStats() 70 { 71 if (_forStick) 72 { 73 for (StickInputId inputId = StickInputId.Left; inputId < StickInputId.Count; inputId++) 74 { 75 (float x, float y) = _currState.GetStick(inputId); 76 77 float value; 78 79 if (x != 0.0f) 80 { 81 value = x; 82 } 83 else if (y != 0.0f) 84 { 85 value = y; 86 } 87 else 88 { 89 continue; 90 } 91 92 _detector.AddInput((GamepadButtonInputId)inputId, value); 93 } 94 } 95 else 96 { 97 for (GamepadButtonInputId inputId = GamepadButtonInputId.A; inputId < GamepadButtonInputId.Count; inputId++) 98 { 99 if (_currState.IsPressed(inputId) && !_prevState.IsPressed(inputId)) 100 { 101 _detector.AddInput(inputId, 1); 102 } 103 104 if (!_currState.IsPressed(inputId) && _prevState.IsPressed(inputId)) 105 { 106 _detector.AddInput(inputId, -1); 107 } 108 } 109 } 110 } 111 112 private class JoystickButtonDetector 113 { 114 private readonly Dictionary<GamepadButtonInputId, InputSummary> _stats; 115 116 public JoystickButtonDetector() 117 { 118 _stats = new Dictionary<GamepadButtonInputId, InputSummary>(); 119 } 120 121 public bool IsAnyButtonPressed() 122 { 123 return _stats.Values.Any(CheckButtonPressed); 124 } 125 126 public IEnumerable<GamepadButtonInputId> GetPressedButtons() 127 { 128 return _stats.Where(kvp => CheckButtonPressed(kvp.Value)).Select(kvp => kvp.Key); 129 } 130 131 public void AddInput(GamepadButtonInputId button, float value) 132 { 133 134 if (!_stats.TryGetValue(button, out InputSummary inputSummary)) 135 { 136 inputSummary = new InputSummary(); 137 _stats.Add(button, inputSummary); 138 } 139 140 inputSummary.AddInput(value); 141 } 142 143 public override string ToString() 144 { 145 StringWriter writer = new(); 146 147 foreach (var kvp in _stats) 148 { 149 writer.WriteLine($"Button {kvp.Key} -> {kvp.Value}"); 150 } 151 152 return writer.ToString(); 153 } 154 155 private bool CheckButtonPressed(InputSummary sequence) 156 { 157 float distance = Math.Abs(sequence.Min - sequence.Avg) + Math.Abs(sequence.Max - sequence.Avg); 158 return distance > 1.5; // distance range [0, 2] 159 } 160 } 161 162 private class InputSummary 163 { 164 public float Min, Max, Sum, Avg; 165 166 public int NumSamples; 167 168 public InputSummary() 169 { 170 Min = float.MaxValue; 171 Max = float.MinValue; 172 Sum = 0; 173 NumSamples = 0; 174 Avg = 0; 175 } 176 177 public void AddInput(float value) 178 { 179 Min = Math.Min(Min, value); 180 Max = Math.Max(Max, value); 181 Sum += value; 182 NumSamples += 1; 183 Avg = Sum / NumSamples; 184 } 185 186 public override string ToString() 187 { 188 return $"Avg: {Avg} Min: {Min} Max: {Max} Sum: {Sum} NumSamples: {NumSamples}"; 189 } 190 } 191 } 192 }