using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Ryujinx.Input.Assigner
{
/// <summary>
/// <see cref="IButtonAssigner"/> implementation for regular <see cref="IGamepad"/>.
/// </summary>
public class GamepadButtonAssigner : IButtonAssigner
private IGamepad _gamepad;
private GamepadStateSnapshot _currState;
private GamepadStateSnapshot _prevState;
private JoystickButtonDetector _detector;
private bool _forStick;
public GamepadButtonAssigner(IGamepad gamepad, float triggerThreshold, bool forStick)
_gamepad = gamepad;
_detector = new JoystickButtonDetector();
_forStick = forStick;
_gamepad?.SetTriggerThreshold(triggerThreshold);
}
public void Initialize()
if (_gamepad != null)
_currState = _gamepad.GetStateSnapshot();
_prevState = _currState;
public void ReadInput()
CollectButtonStats();
public bool HasAnyButtonPressed()
return _detector.HasAnyButtonPressed();
public bool ShouldCancel()
return _gamepad == null || !_gamepad.IsConnected;
public string GetPressedButton()
IEnumerable<GamepadButtonInputId> pressedButtons = _detector.GetPressedButtons();
if (pressedButtons.Any())
return !_forStick ? pressedButtons.First().ToString() : ((StickInputId)pressedButtons.First()).ToString();
return "";
private void CollectButtonStats()
if (_forStick)
for (StickInputId inputId = StickInputId.Left; inputId < StickInputId.Count; inputId++)
(float x, float y) = _currState.GetStick(inputId);
float value;
if (x != 0.0f)
value = x;
else if (y != 0.0f)
value = y;
else
continue;
_detector.AddInput((GamepadButtonInputId)inputId, value);
for (GamepadButtonInputId inputId = GamepadButtonInputId.A; inputId < GamepadButtonInputId.Count; inputId++)
if (_currState.IsPressed(inputId) && !_prevState.IsPressed(inputId))
_detector.AddInput(inputId, 1);
if (!_currState.IsPressed(inputId) && _prevState.IsPressed(inputId))
_detector.AddInput(inputId, -1);
private class JoystickButtonDetector
private Dictionary<GamepadButtonInputId, InputSummary> _stats;
public JoystickButtonDetector()
_stats = new Dictionary<GamepadButtonInputId, InputSummary>();
return _stats.Values.Any(CheckButtonPressed);
public IEnumerable<GamepadButtonInputId> GetPressedButtons()
return _stats.Where(kvp => CheckButtonPressed(kvp.Value)).Select(kvp => kvp.Key);
public void AddInput(GamepadButtonInputId button, float value)
InputSummary inputSummary;
if (!_stats.TryGetValue(button, out inputSummary))
inputSummary = new InputSummary();
_stats.Add(button, inputSummary);
inputSummary.AddInput(value);
public override string ToString()
StringWriter writer = new StringWriter();
foreach (var kvp in _stats)
writer.WriteLine($"Button {kvp.Key} -> {kvp.Value}");
return writer.ToString();
private bool CheckButtonPressed(InputSummary sequence)
float distance = Math.Abs(sequence.Min - sequence.Avg) + Math.Abs(sequence.Max - sequence.Avg);
return distance > 1.5; // distance range [0, 2]
private class InputSummary
public float Min, Max, Sum, Avg;
public int NumSamples;
public InputSummary()
Min = float.MaxValue;
Max = float.MinValue;
Sum = 0;
NumSamples = 0;
Avg = 0;
public void AddInput(float value)
Min = Math.Min(Min, value);
Max = Math.Max(Max, value);
Sum += value;
NumSamples += 1;
Avg = Sum / NumSamples;
return $"Avg: {Avg} Min: {Min} Max: {Max} Sum: {Sum} NumSamples: {NumSamples}";