/ src / Ryujinx.Input / Assigner / GamepadButtonAssigner.cs
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  }