using OpenTK.Input; using Ryujinx.Common.Configuration.Hid; using System.Collections.Generic; using System; using System.IO; namespace Ryujinx.Ui.Input { class JoystickButtonAssigner : ButtonAssigner { private int _index; private double _triggerThreshold; private JoystickState _currState; private JoystickState _prevState; private JoystickButtonDetector _detector; public JoystickButtonAssigner(int index, double triggerThreshold) { _index = index; _triggerThreshold = triggerThreshold; _detector = new JoystickButtonDetector(); } public void Init() { _currState = Joystick.GetState(_index); _prevState = _currState; } public void ReadInput() { _prevState = _currState; _currState = Joystick.GetState(_index); CollectButtonStats(); } public bool HasAnyButtonPressed() { return _detector.HasAnyButtonPressed(); } public bool ShouldCancel() { return Mouse.GetState().IsAnyButtonDown || Keyboard.GetState().IsAnyKeyDown; } public string GetPressedButton() { List pressedButtons = _detector.GetPressedButtons(); // Reverse list so axis button take precedence when more than one button is recognized. pressedButtons.Reverse(); return pressedButtons.Count > 0 ? pressedButtons[0].ToString() : ""; } private void CollectButtonStats() { JoystickCapabilities capabilities = Joystick.GetCapabilities(_index); ControllerInputId pressedButton; // Buttons for (int i = 0; i != capabilities.ButtonCount; i++) { if (_currState.IsButtonDown(i) && _prevState.IsButtonUp(i)) { Enum.TryParse($"Button{i}", out pressedButton); _detector.AddInput(pressedButton, 1); } if (_currState.IsButtonUp(i) && _prevState.IsButtonDown(i)) { Enum.TryParse($"Button{i}", out pressedButton); _detector.AddInput(pressedButton, -1); } } // Axis for (int i = 0; i != capabilities.AxisCount; i++) { float axisValue = _currState.GetAxis(i); Enum.TryParse($"Axis{i}", out pressedButton); _detector.AddInput(pressedButton, axisValue); } // Hats for (int i = 0; i != capabilities.HatCount; i++) { string currPos = GetHatPosition(_currState.GetHat((JoystickHat)i)); string prevPos = GetHatPosition(_prevState.GetHat((JoystickHat)i)); if (currPos == prevPos) { continue; } if (currPos != "") { Enum.TryParse($"Hat{i}{currPos}", out pressedButton); _detector.AddInput(pressedButton, 1); } if (prevPos != "") { Enum.TryParse($"Hat{i}{prevPos}", out pressedButton); _detector.AddInput(pressedButton, -1); } } } private string GetHatPosition(JoystickHatState hatState) { if (hatState.IsUp) return "Up"; if (hatState.IsDown) return "Down"; if (hatState.IsLeft) return "Left"; if (hatState.IsRight) return "Right"; return ""; } private class JoystickButtonDetector { private Dictionary _stats; public JoystickButtonDetector() { _stats = new Dictionary(); } public bool HasAnyButtonPressed() { foreach (var inputSummary in _stats.Values) { if (checkButtonPressed(inputSummary)) { return true; } } return false; } public List GetPressedButtons() { List pressedButtons = new List(); foreach (var kvp in _stats) { if (!checkButtonPressed(kvp.Value)) { continue; } pressedButtons.Add(kvp.Key); } return pressedButtons; } public void AddInput(ControllerInputId 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() { TextWriter 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; } public override string ToString() { return $"Avg: {Avg} Min: {Min} Max: {Max} Sum: {Sum} NumSamples: {NumSamples}"; } } } }