aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Input/Assigner
diff options
context:
space:
mode:
Diffstat (limited to 'Ryujinx.Input/Assigner')
-rw-r--r--Ryujinx.Input/Assigner/GamepadButtonAssigner.cs198
-rw-r--r--Ryujinx.Input/Assigner/IButtonAssigner.cs36
-rw-r--r--Ryujinx.Input/Assigner/KeyboardKeyAssigner.cs50
3 files changed, 284 insertions, 0 deletions
diff --git a/Ryujinx.Input/Assigner/GamepadButtonAssigner.cs b/Ryujinx.Input/Assigner/GamepadButtonAssigner.cs
new file mode 100644
index 00000000..e3aaf8b1
--- /dev/null
+++ b/Ryujinx.Input/Assigner/GamepadButtonAssigner.cs
@@ -0,0 +1,198 @@
+using System.Collections.Generic;
+using System;
+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()
+ {
+ if (_gamepad != null)
+ {
+ _prevState = _currState;
+ _currState = _gamepad.GetStateSnapshot();
+ }
+
+ 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);
+ }
+ }
+ else
+ {
+ 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>();
+ }
+
+ public bool HasAnyButtonPressed()
+ {
+ 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;
+ }
+
+ public override string ToString()
+ {
+ return $"Avg: {Avg} Min: {Min} Max: {Max} Sum: {Sum} NumSamples: {NumSamples}";
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Input/Assigner/IButtonAssigner.cs b/Ryujinx.Input/Assigner/IButtonAssigner.cs
new file mode 100644
index 00000000..021736df
--- /dev/null
+++ b/Ryujinx.Input/Assigner/IButtonAssigner.cs
@@ -0,0 +1,36 @@
+namespace Ryujinx.Input.Assigner
+{
+ /// <summary>
+ /// An interface that allows to gather the driver input info to assign to a button on the UI.
+ /// </summary>
+ public interface IButtonAssigner
+ {
+ /// <summary>
+ /// Initialize the button assigner.
+ /// </summary>
+ void Initialize();
+
+ /// <summary>
+ /// Read input.
+ /// </summary>
+ void ReadInput();
+
+ /// <summary>
+ /// Check if a button was pressed.
+ /// </summary>
+ /// <returns>True if a button was pressed</returns>
+ bool HasAnyButtonPressed();
+
+ /// <summary>
+ /// Indicate if the user of this API should cancel operations. This is triggered for example when a gamepad get disconnected or when a user cancel assignation operations.
+ /// </summary>
+ /// <returns>True if the user of this API should cancel operations</returns>
+ bool ShouldCancel();
+
+ /// <summary>
+ /// Get the pressed button that was read in <see cref="ReadInput"/> by the button assigner.
+ /// </summary>
+ /// <returns>The pressed button that was read</returns>
+ string GetPressedButton();
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Input/Assigner/KeyboardKeyAssigner.cs b/Ryujinx.Input/Assigner/KeyboardKeyAssigner.cs
new file mode 100644
index 00000000..23ae3655
--- /dev/null
+++ b/Ryujinx.Input/Assigner/KeyboardKeyAssigner.cs
@@ -0,0 +1,50 @@
+namespace Ryujinx.Input.Assigner
+{
+ /// <summary>
+ /// <see cref="IButtonAssigner"/> implementation for <see cref="IKeyboard"/>.
+ /// </summary>
+ public class KeyboardKeyAssigner : IButtonAssigner
+ {
+ private IKeyboard _keyboard;
+
+ private KeyboardStateSnapshot _keyboardState;
+
+ public KeyboardKeyAssigner(IKeyboard keyboard)
+ {
+ _keyboard = keyboard;
+ }
+
+ public void Initialize() { }
+
+ public void ReadInput()
+ {
+ _keyboardState = _keyboard.GetKeyboardStateSnapshot();
+ }
+
+ public bool HasAnyButtonPressed()
+ {
+ return GetPressedButton().Length != 0;
+ }
+
+ public bool ShouldCancel()
+ {
+ return _keyboardState.IsPressed(Key.Escape);
+ }
+
+ public string GetPressedButton()
+ {
+ string keyPressed = "";
+
+ for (Key key = Key.Unknown; key < Key.Count; key++)
+ {
+ if (_keyboardState.IsPressed(key))
+ {
+ keyPressed = key.ToString();
+ break;
+ }
+ }
+
+ return !ShouldCancel() ? keyPressed : "";
+ }
+ }
+} \ No newline at end of file