aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Ryujinx/Ui/Input/ButtonAssigner.cs17
-rw-r--r--Ryujinx/Ui/Input/JoystickButtonAssigner.cs227
-rw-r--r--Ryujinx/Ui/Input/KeyboardKeyAssigner.cs51
-rw-r--r--Ryujinx/Ui/Windows/ControllerWindow.cs159
4 files changed, 341 insertions, 113 deletions
diff --git a/Ryujinx/Ui/Input/ButtonAssigner.cs b/Ryujinx/Ui/Input/ButtonAssigner.cs
new file mode 100644
index 00000000..ff32b106
--- /dev/null
+++ b/Ryujinx/Ui/Input/ButtonAssigner.cs
@@ -0,0 +1,17 @@
+using Ryujinx.Common.Configuration.Hid;
+
+namespace Ryujinx.Ui.Input
+{
+ interface ButtonAssigner
+ {
+ void Init();
+
+ void ReadInput();
+
+ bool HasAnyButtonPressed();
+
+ bool ShouldCancel();
+
+ string GetPressedButton();
+ }
+} \ No newline at end of file
diff --git a/Ryujinx/Ui/Input/JoystickButtonAssigner.cs b/Ryujinx/Ui/Input/JoystickButtonAssigner.cs
new file mode 100644
index 00000000..481221ac
--- /dev/null
+++ b/Ryujinx/Ui/Input/JoystickButtonAssigner.cs
@@ -0,0 +1,227 @@
+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<ControllerInputId> 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<ControllerInputId, InputSummary> _stats;
+
+ public JoystickButtonDetector()
+ {
+ _stats = new Dictionary<ControllerInputId, InputSummary>();
+ }
+
+ public bool HasAnyButtonPressed()
+ {
+ foreach (var inputSummary in _stats.Values)
+ {
+ if (checkButtonPressed(inputSummary))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public List<ControllerInputId> GetPressedButtons()
+ {
+ List<ControllerInputId> pressedButtons = new List<ControllerInputId>();
+
+ 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}";
+ }
+ }
+ }
+}
diff --git a/Ryujinx/Ui/Input/KeyboardKeyAssigner.cs b/Ryujinx/Ui/Input/KeyboardKeyAssigner.cs
new file mode 100644
index 00000000..2a29c366
--- /dev/null
+++ b/Ryujinx/Ui/Input/KeyboardKeyAssigner.cs
@@ -0,0 +1,51 @@
+using OpenTK.Input;
+using System;
+using Key = Ryujinx.Configuration.Hid.Key;
+
+namespace Ryujinx.Ui.Input
+{
+ class KeyboardKeyAssigner : ButtonAssigner
+ {
+ private int _index;
+
+ private KeyboardState _keyboardState;
+
+ public KeyboardKeyAssigner(int index)
+ {
+ _index = index;
+ }
+
+ public void Init() { }
+
+ public void ReadInput()
+ {
+ _keyboardState = KeyboardController.GetKeyboardState(_index);
+ }
+
+ public bool HasAnyButtonPressed()
+ {
+ return _keyboardState.IsAnyKeyDown;
+ }
+
+ public bool ShouldCancel()
+ {
+ return Mouse.GetState().IsAnyButtonDown || Keyboard.GetState().IsKeyDown(OpenTK.Input.Key.Escape);
+ }
+
+ public string GetPressedButton()
+ {
+ string keyPressed = "";
+
+ foreach (Key key in Enum.GetValues(typeof(Key)))
+ {
+ if (_keyboardState.IsKeyDown((OpenTK.Input.Key)key))
+ {
+ keyPressed = key.ToString();
+ break;
+ }
+ }
+
+ return !ShouldCancel() ? keyPressed : "";
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx/Ui/Windows/ControllerWindow.cs b/Ryujinx/Ui/Windows/ControllerWindow.cs
index e06c6c6b..67c02a0b 100644
--- a/Ryujinx/Ui/Windows/ControllerWindow.cs
+++ b/Ryujinx/Ui/Windows/ControllerWindow.cs
@@ -4,6 +4,7 @@ using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Utilities;
using Ryujinx.Configuration;
+using Ryujinx.Ui.Input;
using Ryujinx.Ui.Widgets;
using System;
using System.Collections.Generic;
@@ -584,73 +585,6 @@ namespace Ryujinx.Ui.Windows
return null;
}
- private static bool IsAnyKeyPressed(out Key pressedKey, int index)
- {
- KeyboardState keyboardState = KeyboardController.GetKeyboardState(index);
-
- foreach (Key key in Enum.GetValues(typeof(Key)))
- {
- if (keyboardState.IsKeyDown((OpenTK.Input.Key)key))
- {
- pressedKey = key;
-
- return true;
- }
- }
-
- pressedKey = Key.Unbound;
-
- return false;
- }
-
- private static bool IsAnyButtonPressed(out ControllerInputId pressedButton, int index, double triggerThreshold)
- {
- JoystickState joystickState = Joystick.GetState(index);
- JoystickCapabilities joystickCapabilities = Joystick.GetCapabilities(index);
-
- //Buttons
- for (int i = 0; i != joystickCapabilities.ButtonCount; i++)
- {
- if (joystickState.IsButtonDown(i))
- {
- Enum.TryParse($"Button{i}", out pressedButton);
- return true;
- }
- }
-
- //Axis
- for (int i = 0; i != joystickCapabilities.AxisCount; i++)
- {
- if (joystickState.GetAxis(i) > 0.5f && joystickState.GetAxis(i) > triggerThreshold)
- {
- Enum.TryParse($"Axis{i}", out pressedButton);
-
- return true;
- }
- }
-
- //Hats
- for (int i = 0; i != joystickCapabilities.HatCount; i++)
- {
- JoystickHatState hatState = joystickState.GetHat((JoystickHat)i);
- string pos = null;
-
- if (hatState.IsUp) pos = "Up";
- if (hatState.IsDown) pos = "Down";
- if (hatState.IsLeft) pos = "Left";
- if (hatState.IsRight) pos = "Right";
- if (pos == null) continue;
-
- Enum.TryParse($"Hat{i}{pos}", out pressedButton);
-
- return true;
- }
-
- pressedButton = ControllerInputId.Unbound;
-
- return false;
- }
-
private string GetProfileBasePath()
{
string path = AppDataManager.ProfilesDirPath;
@@ -690,6 +624,31 @@ namespace Ryujinx.Ui.Windows
_refreshInputDevicesButton.SetStateFlags(StateFlags.Normal, true);
}
+ private ButtonAssigner CreateButtonAssigner()
+ {
+ int index = int.Parse(_inputDevice.ActiveId.Split("/")[1]);
+
+ ButtonAssigner assigner;
+
+ if (_inputDevice.ActiveId.StartsWith("keyboard"))
+ {
+ assigner = new KeyboardKeyAssigner(index);
+ }
+ else if (_inputDevice.ActiveId.StartsWith("controller"))
+ {
+ // TODO: triggerThresold is passed but not used by JoystickButtonAssigner. Should it be used for key binding?.
+ // Note that, like left and right sticks, ZL and ZR triggers are treated as axis.
+ // The problem is then how to decide which axis should use triggerThresold.
+ assigner = new JoystickButtonAssigner(index, _controllerTriggerThreshold.Value);
+ }
+ else
+ {
+ throw new Exception("Controller not supported");
+ }
+
+ return assigner;
+ }
+
private void Button_Pressed(object sender, EventArgs args)
{
if (_isWaitingForInput)
@@ -697,67 +656,41 @@ namespace Ryujinx.Ui.Windows
return;
}
+ ButtonAssigner assigner = CreateButtonAssigner();
+
_isWaitingForInput = true;
Thread inputThread = new Thread(() =>
{
- Button button = (ToggleButton)sender;
+ assigner.Init();
- if (_inputDevice.ActiveId.StartsWith("keyboard"))
+ while (true)
{
- Key pressedKey;
+ Thread.Sleep(10);
+ assigner.ReadInput();
- int index = int.Parse(_inputDevice.ActiveId.Split("/")[1]);
- while (!IsAnyKeyPressed(out pressedKey, index))
+ if (assigner.HasAnyButtonPressed() || assigner.ShouldCancel())
{
- if (Mouse.GetState().IsAnyButtonDown || Keyboard.GetState().IsKeyDown(OpenTK.Input.Key.Escape))
- {
- Application.Invoke(delegate
- {
- button.SetStateFlags(StateFlags.Normal, true);
- });
-
- _isWaitingForInput = false;
-
- return;
- }
+ break;
}
-
- Application.Invoke(delegate
- {
- button.Label = pressedKey.ToString();
- button.SetStateFlags(StateFlags.Normal, true);
- });
}
- else if (_inputDevice.ActiveId.StartsWith("controller"))
- {
- ControllerInputId pressedButton;
- int index = int.Parse(_inputDevice.ActiveId.Split("/")[1]);
- while (!IsAnyButtonPressed(out pressedButton, index, _controllerTriggerThreshold.Value))
- {
- if (Mouse.GetState().IsAnyButtonDown || Keyboard.GetState().IsAnyKeyDown)
- {
- Application.Invoke(delegate
- {
- button.SetStateFlags(StateFlags.Normal, true);
- });
+ string pressedButton = assigner.GetPressedButton();
- _isWaitingForInput = false;
+ ToggleButton button = (ToggleButton) sender;
- return;
- }
- }
-
- Application.Invoke(delegate
+ Application.Invoke(delegate
+ {
+ if (pressedButton != "")
{
- button.Label = pressedButton.ToString();
- button.SetStateFlags(StateFlags.Normal, true);
- });
- }
-
- _isWaitingForInput = false;
+ button.Label = pressedButton;
+ }
+
+ button.Active = false;
+ _isWaitingForInput = false;
+ });
});
+
inputThread.Name = "GUI.InputThread";
inputThread.IsBackground = true;
inputThread.Start();