Improve gamepad input handling (#901)
Co-authored-by: Florian Ostertag <flokuep@users.noreply.github.com>
This commit is contained in:
1 parent
611591be1d
commit
e69f62809a
1 file changed
+384
-399
+384
-399
@@ -23,53 +23,57 @@ using System.Windows.Forms;
|
||||
using OpenTK.Input;
|
||||
using Vocaluxe.Base;
|
||||
using VocaluxeLib;
|
||||
using ButtonState = OpenTK.Input.ButtonState;
|
||||
|
||||
namespace Vocaluxe.Lib.Input
|
||||
{
|
||||
class CGamePad : CControllerFramework
|
||||
{
|
||||
private const int MaxGamePads = 4;
|
||||
private const int PollSleepMs = 5;
|
||||
private const int PollIntervalMs = 5;
|
||||
private const int ReconnectWaitMs = 1000;
|
||||
private const int ErrorBackoffMs = 2000;
|
||||
private const int ThreadJoinTimeoutMs = 2000;
|
||||
|
||||
private const int InitialRepeatDelayMs = 400;
|
||||
private const int KeyRepeatDelayMs = 100;
|
||||
|
||||
private const float LeftStickDeadZone = 0.8f;
|
||||
private const float RightStickMouseDeadZone = 0.15f;
|
||||
private const float TriggerThreshold = 0.8f;
|
||||
private const float LimitFactor = 1.0f;
|
||||
private const float MouseSpeed = 25.0f;
|
||||
private const float MouseAxisEpsilon = 0.001f;
|
||||
private const float StickPressThreshold = 0.8f;
|
||||
private const float StickReleaseThreshold = 0.6f;
|
||||
private const float TriggerPressThreshold = 0.8f;
|
||||
private const float TriggerReleaseThreshold = 0.6f;
|
||||
|
||||
private const float RightStickDeadZone = 0.15f;
|
||||
private const float MouseSpeedPxPerSecond = 1500.0f;
|
||||
private const float MaxMouseStepSeconds = 0.1f;
|
||||
|
||||
private const int ConnectRumblePulses = 3;
|
||||
private const int ConnectRumblePulseMs = 125;
|
||||
|
||||
private readonly object _Sync = new object();
|
||||
|
||||
private readonly Stopwatch _dpadDownTimer = new Stopwatch();
|
||||
private readonly Stopwatch _dpadUpTimer = new Stopwatch();
|
||||
private readonly Stopwatch _dpadLeftTimer = new Stopwatch();
|
||||
private readonly Stopwatch _dpadRightTimer = new Stopwatch();
|
||||
private readonly Stopwatch _leftStickDownTimer = new Stopwatch();
|
||||
private readonly Stopwatch _leftStickUpTimer = new Stopwatch();
|
||||
private readonly Stopwatch _leftStickLeftTimer = new Stopwatch();
|
||||
private readonly Stopwatch _leftStickRightTimer = new Stopwatch();
|
||||
private readonly Stopwatch _leftTriggerTimer = new Stopwatch();
|
||||
private readonly Stopwatch _rightTriggerTimer = new Stopwatch();
|
||||
|
||||
private int _GamePadIndex = -1;
|
||||
private GamePadState _oldButtonStates;
|
||||
private Thread _handlerThread;
|
||||
private AutoResetEvent _evTerminate;
|
||||
private volatile bool _active;
|
||||
private CRumbleTimer _rumbleTimer;
|
||||
|
||||
private float _mouseX;
|
||||
private float _mouseY;
|
||||
|
||||
private bool Connected
|
||||
private readonly List<Keys> _PendingKeys = new List<Keys>();
|
||||
private readonly Stopwatch _MouseTimer = new Stopwatch();
|
||||
private readonly CRepeatingInput[] _RepeatingInputs =
|
||||
{
|
||||
get { return _GamePadIndex != -1; }
|
||||
}
|
||||
new CRepeatingInput(Keys.Up, (s, was) => s.DPad.IsUp || _IsAxisPressed(s.ThumbSticks.Left.Y, was)),
|
||||
new CRepeatingInput(Keys.Down, (s, was) => s.DPad.IsDown || _IsAxisPressed(-s.ThumbSticks.Left.Y, was)),
|
||||
new CRepeatingInput(Keys.Left, (s, was) => s.DPad.IsLeft || _IsAxisPressed(-s.ThumbSticks.Left.X, was)),
|
||||
new CRepeatingInput(Keys.Right, (s, was) => s.DPad.IsRight || _IsAxisPressed(s.ThumbSticks.Left.X, was)),
|
||||
new CRepeatingInput(Keys.PageUp, (s, was) => _IsTriggerPressed(s.Triggers.Left, was)),
|
||||
new CRepeatingInput(Keys.PageDown, (s, was) => _IsTriggerPressed(s.Triggers.Right, was))
|
||||
};
|
||||
|
||||
private volatile int _GamePadIndex = -1;
|
||||
private volatile bool _Active;
|
||||
private int _LastConnectedIndex = -1;
|
||||
private GamePadState _OldState;
|
||||
private Thread _HandlerThread;
|
||||
private ManualResetEvent _EvTerminate;
|
||||
private CRumbleTimer _RumbleTimer;
|
||||
|
||||
private float _MouseX;
|
||||
private float _MouseY;
|
||||
|
||||
public override string GetName()
|
||||
{
|
||||
@@ -78,122 +82,127 @@ namespace Vocaluxe.Lib.Input
|
||||
|
||||
public override bool Init()
|
||||
{
|
||||
_StopThread();
|
||||
|
||||
if (!base.Init())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_rumbleTimer = new CRumbleTimer();
|
||||
_evTerminate = new AutoResetEvent(false);
|
||||
_oldButtonStates = new GamePadState();
|
||||
_GamePadIndex = -1;
|
||||
_active = false;
|
||||
_handlerThread = null;
|
||||
lock (_Sync)
|
||||
{
|
||||
_RumbleTimer = new CRumbleTimer();
|
||||
}
|
||||
|
||||
_mouseX = CSettings.RenderW / 2.0f;
|
||||
_mouseY = CSettings.RenderH / 2.0f;
|
||||
_GamePadIndex = -1;
|
||||
_LastConnectedIndex = -1;
|
||||
_ResetInputState();
|
||||
|
||||
_MouseX = CSettings.RenderW / 2.0f;
|
||||
_MouseY = CSettings.RenderH / 2.0f;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Connect()
|
||||
{
|
||||
if (_active)
|
||||
if (_Active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_evTerminate == null)
|
||||
if (_HandlerThread != null)
|
||||
{
|
||||
_evTerminate = new AutoResetEvent(false);
|
||||
}
|
||||
|
||||
if (_handlerThread == null)
|
||||
{
|
||||
_handlerThread = new Thread(_MainLoop)
|
||||
if (_HandlerThread.IsAlive)
|
||||
{
|
||||
Name = "GamePad",
|
||||
Priority = ThreadPriority.BelowNormal,
|
||||
IsBackground = true
|
||||
};
|
||||
Debug.WriteLine("CGamePad: Previous handler thread is still running, cannot connect.");
|
||||
return;
|
||||
}
|
||||
|
||||
_HandlerThread = null;
|
||||
}
|
||||
|
||||
_active = true;
|
||||
_handlerThread.Start();
|
||||
if (_EvTerminate == null)
|
||||
{
|
||||
_EvTerminate = new ManualResetEvent(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_EvTerminate.Reset();
|
||||
}
|
||||
|
||||
var evTerminate = _EvTerminate;
|
||||
_Active = true;
|
||||
_HandlerThread = new Thread(() => _MainLoop(evTerminate))
|
||||
{
|
||||
Name = "GamePad",
|
||||
Priority = ThreadPriority.BelowNormal,
|
||||
IsBackground = true
|
||||
};
|
||||
_HandlerThread.Start();
|
||||
}
|
||||
|
||||
public override void Disconnect()
|
||||
{
|
||||
Close();
|
||||
_StopThread();
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
_active = false;
|
||||
|
||||
if (_evTerminate != null)
|
||||
{
|
||||
_evTerminate.Set();
|
||||
}
|
||||
|
||||
if (_handlerThread != null)
|
||||
{
|
||||
if (!_handlerThread.Join(ThreadJoinTimeoutMs))
|
||||
{
|
||||
Debug.WriteLine("CGamePad: Handler thread did not terminate within timeout.");
|
||||
}
|
||||
|
||||
_handlerThread = null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (_GamePadIndex != -1)
|
||||
{
|
||||
GamePad.SetVibration(_GamePadIndex, 0.0f, 0.0f);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine("CGamePad: Failed to stop vibration while closing: " + ex);
|
||||
}
|
||||
|
||||
_GamePadIndex = -1;
|
||||
|
||||
if (_evTerminate != null)
|
||||
{
|
||||
_evTerminate.Dispose();
|
||||
_evTerminate = null;
|
||||
}
|
||||
|
||||
_StopThread();
|
||||
base.Close();
|
||||
}
|
||||
|
||||
public override bool IsConnected()
|
||||
{
|
||||
return Connected;
|
||||
return _GamePadIndex != -1;
|
||||
}
|
||||
|
||||
public override void SetRumble(float duration)
|
||||
{
|
||||
lock (_Sync)
|
||||
{
|
||||
if (_rumbleTimer != null)
|
||||
if (_RumbleTimer != null)
|
||||
{
|
||||
_rumbleTimer.Set(duration);
|
||||
_RumbleTimer.Set(duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void _MainLoop()
|
||||
private void _StopThread()
|
||||
{
|
||||
_Active = false;
|
||||
|
||||
if (_EvTerminate != null)
|
||||
{
|
||||
_EvTerminate.Set();
|
||||
}
|
||||
|
||||
if (_HandlerThread != null)
|
||||
{
|
||||
if (!_HandlerThread.Join(ThreadJoinTimeoutMs))
|
||||
{
|
||||
Debug.WriteLine("CGamePad: Handler thread did not terminate within timeout.");
|
||||
return;
|
||||
}
|
||||
|
||||
_HandlerThread = null;
|
||||
}
|
||||
|
||||
if (_EvTerminate != null)
|
||||
{
|
||||
_EvTerminate.Dispose();
|
||||
_EvTerminate = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void _MainLoop(WaitHandle evTerminate)
|
||||
{
|
||||
try
|
||||
{
|
||||
while (_active)
|
||||
while (_Active && !evTerminate.WaitOne(PollIntervalMs))
|
||||
{
|
||||
Thread.Sleep(PollSleepMs);
|
||||
|
||||
if (!_EnsureConnected())
|
||||
if (!_EnsureConnected(evTerminate))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -204,89 +213,159 @@ namespace Vocaluxe.Lib.Input
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_HandleLoopException(ex);
|
||||
Debug.WriteLine("CGamePad: Exception in input loop: " + ex);
|
||||
_HandleDisconnect(false);
|
||||
evTerminate.WaitOne(ErrorBackoffMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_StopVibrationBestEffort(_GamePadIndex);
|
||||
_StopVibration(_GamePadIndex);
|
||||
_GamePadIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private bool _EnsureConnected()
|
||||
private bool _EnsureConnected(WaitHandle evTerminate)
|
||||
{
|
||||
if (Connected)
|
||||
if (_GamePadIndex != -1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_DoConnect())
|
||||
if (_DoConnect(evTerminate))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_evTerminate != null)
|
||||
{
|
||||
_evTerminate.WaitOne(ReconnectWaitMs);
|
||||
}
|
||||
|
||||
evTerminate.WaitOne(ReconnectWaitMs);
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool _DoConnect(WaitHandle evTerminate)
|
||||
{
|
||||
var index = _FindConnectedPad();
|
||||
if (index == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_ResetInputState();
|
||||
_GamePadIndex = index;
|
||||
|
||||
var isNewPad = index != _LastConnectedIndex;
|
||||
_LastConnectedIndex = index;
|
||||
if (isNewPad)
|
||||
{
|
||||
_PlayConnectRumble(index, evTerminate);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int _FindConnectedPad()
|
||||
{
|
||||
for (var i = 0; i < MaxGamePads; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (GamePad.GetCapabilities(i).IsConnected)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine("CGamePad: Failed to query capabilities for pad " + i + ": " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static void _PlayConnectRumble(int index, WaitHandle evTerminate)
|
||||
{
|
||||
try
|
||||
{
|
||||
for (var i = 0; i < ConnectRumblePulses; i++)
|
||||
{
|
||||
GamePad.SetVibration(index, 1.0f, 1.0f);
|
||||
if (evTerminate.WaitOne(ConnectRumblePulseMs))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
GamePad.SetVibration(index, 0.0f, 0.0f);
|
||||
if (evTerminate.WaitOne(ConnectRumblePulseMs))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine("CGamePad: Failed during connect rumble sequence: " + ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_StopVibration(index);
|
||||
}
|
||||
}
|
||||
|
||||
private void _ProcessCurrentGamePad()
|
||||
{
|
||||
var index = _GamePadIndex;
|
||||
if (index == -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var state = GamePad.GetState(index);
|
||||
if (!state.IsConnected)
|
||||
{
|
||||
_HandleDisconnect(true);
|
||||
return;
|
||||
}
|
||||
|
||||
_UpdateRumble(index);
|
||||
_HandleInput(state);
|
||||
}
|
||||
|
||||
private void _UpdateRumble(int index)
|
||||
{
|
||||
bool startRumble;
|
||||
bool stopRumble;
|
||||
|
||||
lock (_Sync)
|
||||
{
|
||||
startRumble = _rumbleTimer != null && _rumbleTimer.ShouldStart;
|
||||
stopRumble = _rumbleTimer != null && _rumbleTimer.ShouldStop;
|
||||
}
|
||||
|
||||
var currentIndex = _GamePadIndex;
|
||||
if (currentIndex == -1)
|
||||
{
|
||||
return;
|
||||
startRumble = _RumbleTimer != null && _RumbleTimer.ShouldStart;
|
||||
stopRumble = _RumbleTimer != null && _RumbleTimer.ShouldStop;
|
||||
}
|
||||
|
||||
if (startRumble)
|
||||
{
|
||||
GamePad.SetVibration(currentIndex, 1.0f, 1.0f);
|
||||
GamePad.SetVibration(index, 1.0f, 1.0f);
|
||||
}
|
||||
else if (stopRumble)
|
||||
{
|
||||
GamePad.SetVibration(currentIndex, 0.0f, 0.0f);
|
||||
GamePad.SetVibration(index, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
var state = GamePad.GetState(currentIndex);
|
||||
|
||||
if (!GamePad.GetCapabilities(currentIndex).IsConnected)
|
||||
{
|
||||
_HandleDisconnect(currentIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
_HandleButtons(state);
|
||||
}
|
||||
|
||||
private void _HandleLoopException(Exception ex)
|
||||
private void _HandleDisconnect(bool padRemoved)
|
||||
{
|
||||
Debug.WriteLine("CGamePad: Exception in input loop: " + ex);
|
||||
_HandleDisconnect(_GamePadIndex);
|
||||
}
|
||||
|
||||
private void _HandleDisconnect(int gamePadIndex)
|
||||
{
|
||||
_StopVibrationBestEffort(gamePadIndex);
|
||||
_StopVibration(_GamePadIndex);
|
||||
_GamePadIndex = -1;
|
||||
_oldButtonStates = new GamePadState();
|
||||
_ResetAllRepeatTimers();
|
||||
|
||||
if (padRemoved)
|
||||
{
|
||||
_LastConnectedIndex = -1;
|
||||
}
|
||||
|
||||
_ResetInputState();
|
||||
}
|
||||
|
||||
private static void _StopVibrationBestEffort(int gamePadIndex)
|
||||
private static void _StopVibration(int gamePadIndex)
|
||||
{
|
||||
if (gamePadIndex == -1)
|
||||
{
|
||||
@@ -297,268 +376,174 @@ namespace Vocaluxe.Lib.Input
|
||||
{
|
||||
GamePad.SetVibration(gamePadIndex, 0.0f, 0.0f);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore: stopping vibration during disconnect/reset is best-effort only.
|
||||
}
|
||||
}
|
||||
|
||||
private void _HandleButtons(GamePadState buttonStates)
|
||||
{
|
||||
var leftClickTriggered =
|
||||
buttonStates.Buttons.LeftShoulder == OpenTK.Input.ButtonState.Pressed &&
|
||||
_oldButtonStates.Buttons.LeftShoulder == OpenTK.Input.ButtonState.Released;
|
||||
|
||||
var rightClickTriggered =
|
||||
buttonStates.Buttons.RightShoulder == OpenTK.Input.ButtonState.Pressed &&
|
||||
_oldButtonStates.Buttons.RightShoulder == OpenTK.Input.ButtonState.Released;
|
||||
|
||||
leftClickTriggered |=
|
||||
buttonStates.Buttons.RightStick == OpenTK.Input.ButtonState.Pressed &&
|
||||
_oldButtonStates.Buttons.RightStick == OpenTK.Input.ButtonState.Released;
|
||||
|
||||
var keys = new List<Keys>();
|
||||
|
||||
_AddRepeatedKey(
|
||||
keys,
|
||||
buttonStates.DPad.IsDown,
|
||||
_oldButtonStates.DPad.IsDown,
|
||||
_dpadDownTimer,
|
||||
Keys.Down);
|
||||
|
||||
_AddRepeatedKey(
|
||||
keys,
|
||||
buttonStates.DPad.IsUp,
|
||||
_oldButtonStates.DPad.IsUp,
|
||||
_dpadUpTimer,
|
||||
Keys.Up);
|
||||
|
||||
_AddRepeatedKey(
|
||||
keys,
|
||||
buttonStates.DPad.IsLeft,
|
||||
_oldButtonStates.DPad.IsLeft,
|
||||
_dpadLeftTimer,
|
||||
Keys.Left);
|
||||
|
||||
_AddRepeatedKey(
|
||||
keys,
|
||||
buttonStates.DPad.IsRight,
|
||||
_oldButtonStates.DPad.IsRight,
|
||||
_dpadRightTimer,
|
||||
Keys.Right);
|
||||
|
||||
_AddRepeatedKey(
|
||||
keys,
|
||||
buttonStates.ThumbSticks.Left.Y > LeftStickDeadZone,
|
||||
_oldButtonStates.ThumbSticks.Left.Y > LeftStickDeadZone,
|
||||
_leftStickUpTimer,
|
||||
Keys.Up);
|
||||
|
||||
_AddRepeatedKey(
|
||||
keys,
|
||||
buttonStates.ThumbSticks.Left.Y < -LeftStickDeadZone,
|
||||
_oldButtonStates.ThumbSticks.Left.Y < -LeftStickDeadZone,
|
||||
_leftStickDownTimer,
|
||||
Keys.Down);
|
||||
|
||||
_AddRepeatedKey(
|
||||
keys,
|
||||
buttonStates.ThumbSticks.Left.X < -LeftStickDeadZone,
|
||||
_oldButtonStates.ThumbSticks.Left.X < -LeftStickDeadZone,
|
||||
_leftStickLeftTimer,
|
||||
Keys.Left);
|
||||
|
||||
_AddRepeatedKey(
|
||||
keys,
|
||||
buttonStates.ThumbSticks.Left.X > LeftStickDeadZone,
|
||||
_oldButtonStates.ThumbSticks.Left.X > LeftStickDeadZone,
|
||||
_leftStickRightTimer,
|
||||
Keys.Right);
|
||||
|
||||
_AddRepeatedKey(
|
||||
keys,
|
||||
buttonStates.Triggers.Left >= TriggerThreshold,
|
||||
_oldButtonStates.Triggers.Left >= TriggerThreshold,
|
||||
_leftTriggerTimer,
|
||||
Keys.PageUp);
|
||||
|
||||
_AddRepeatedKey(
|
||||
keys,
|
||||
buttonStates.Triggers.Right >= TriggerThreshold,
|
||||
_oldButtonStates.Triggers.Right >= TriggerThreshold,
|
||||
_rightTriggerTimer,
|
||||
Keys.PageDown);
|
||||
|
||||
if (buttonStates.Buttons.Start == OpenTK.Input.ButtonState.Pressed &&
|
||||
_oldButtonStates.Buttons.Start == OpenTK.Input.ButtonState.Released)
|
||||
{
|
||||
keys.Add(Keys.Space);
|
||||
}
|
||||
else if (buttonStates.Buttons.A == OpenTK.Input.ButtonState.Pressed &&
|
||||
_oldButtonStates.Buttons.A == OpenTK.Input.ButtonState.Released)
|
||||
{
|
||||
keys.Add(Keys.Enter);
|
||||
}
|
||||
else if (buttonStates.Buttons.B == OpenTK.Input.ButtonState.Pressed &&
|
||||
_oldButtonStates.Buttons.B == OpenTK.Input.ButtonState.Released)
|
||||
{
|
||||
keys.Add(Keys.Escape);
|
||||
}
|
||||
else if (buttonStates.Buttons.Back == OpenTK.Input.ButtonState.Pressed &&
|
||||
_oldButtonStates.Buttons.Back == OpenTK.Input.ButtonState.Released)
|
||||
{
|
||||
keys.Add(Keys.Back);
|
||||
}
|
||||
|
||||
foreach (var key in keys)
|
||||
{
|
||||
AddKeyEvent(new SKeyEvent(
|
||||
ESender.Gamepad,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
char.MinValue,
|
||||
key));
|
||||
}
|
||||
|
||||
var rightX = buttonStates.ThumbSticks.Right.X;
|
||||
var rightY = buttonStates.ThumbSticks.Right.Y;
|
||||
|
||||
if (Math.Abs(rightX) < RightStickMouseDeadZone)
|
||||
{
|
||||
rightX = 0.0f;
|
||||
}
|
||||
|
||||
if (Math.Abs(rightY) < RightStickMouseDeadZone)
|
||||
{
|
||||
rightY = 0.0f;
|
||||
}
|
||||
|
||||
var hasMouseDelta =
|
||||
Math.Abs(rightX) > MouseAxisEpsilon ||
|
||||
Math.Abs(rightY) > MouseAxisEpsilon;
|
||||
|
||||
if (hasMouseDelta)
|
||||
{
|
||||
_mouseX += rightX * MouseSpeed * LimitFactor;
|
||||
_mouseY -= rightY * MouseSpeed * LimitFactor;
|
||||
}
|
||||
|
||||
_mouseX = Math.Min(CSettings.RenderW, Math.Max(0.0f, _mouseX));
|
||||
_mouseY = Math.Min(CSettings.RenderH, Math.Max(0.0f, _mouseY));
|
||||
|
||||
var mouseMoved =
|
||||
hasMouseDelta ||
|
||||
leftClickTriggered ||
|
||||
rightClickTriggered;
|
||||
|
||||
if (mouseMoved)
|
||||
{
|
||||
AddMouseEvent(new SMouseEvent(
|
||||
ESender.Gamepad,
|
||||
EModifier.None,
|
||||
(int)_mouseX,
|
||||
(int)_mouseY,
|
||||
leftClickTriggered,
|
||||
false,
|
||||
rightClickTriggered,
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false));
|
||||
}
|
||||
|
||||
_oldButtonStates = buttonStates;
|
||||
}
|
||||
|
||||
private static void _AddRepeatedKey(
|
||||
ICollection<Keys> keys,
|
||||
bool isPressed,
|
||||
bool wasPressed,
|
||||
Stopwatch timer,
|
||||
Keys key)
|
||||
{
|
||||
if (isPressed)
|
||||
{
|
||||
if (!wasPressed || timer.ElapsedMilliseconds >= KeyRepeatDelayMs)
|
||||
{
|
||||
keys.Add(key);
|
||||
timer.Restart();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
timer.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
private void _ResetAllRepeatTimers()
|
||||
{
|
||||
_dpadDownTimer.Reset();
|
||||
_dpadUpTimer.Reset();
|
||||
_dpadLeftTimer.Reset();
|
||||
_dpadRightTimer.Reset();
|
||||
_leftStickDownTimer.Reset();
|
||||
_leftStickUpTimer.Reset();
|
||||
_leftStickLeftTimer.Reset();
|
||||
_leftStickRightTimer.Reset();
|
||||
_leftTriggerTimer.Reset();
|
||||
_rightTriggerTimer.Reset();
|
||||
}
|
||||
|
||||
private bool _DoConnect()
|
||||
{
|
||||
_GamePadIndex = -1;
|
||||
|
||||
for (var i = 0; i < MaxGamePads; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (GamePad.GetCapabilities(i).IsConnected)
|
||||
{
|
||||
_GamePadIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine("CGamePad: Failed to query capabilities for pad " + i + ": " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (_GamePadIndex == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_oldButtonStates = new GamePadState();
|
||||
_ResetAllRepeatTimers();
|
||||
|
||||
_mouseX = Math.Min(CSettings.RenderW, Math.Max(0.0f, _mouseX));
|
||||
_mouseY = Math.Min(CSettings.RenderH, Math.Max(0.0f, _mouseY));
|
||||
|
||||
try
|
||||
{
|
||||
GamePad.SetVibration(_GamePadIndex, 1.0f, 1.0f);
|
||||
Thread.Sleep(ConnectRumblePulseMs);
|
||||
GamePad.SetVibration(_GamePadIndex, 0.0f, 0.0f);
|
||||
Thread.Sleep(ConnectRumblePulseMs);
|
||||
GamePad.SetVibration(_GamePadIndex, 1.0f, 1.0f);
|
||||
Thread.Sleep(ConnectRumblePulseMs);
|
||||
GamePad.SetVibration(_GamePadIndex, 0.0f, 0.0f);
|
||||
Thread.Sleep(ConnectRumblePulseMs);
|
||||
GamePad.SetVibration(_GamePadIndex, 1.0f, 1.0f);
|
||||
Thread.Sleep(ConnectRumblePulseMs);
|
||||
GamePad.SetVibration(_GamePadIndex, 0.0f, 0.0f);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine("CGamePad: Failed during connect rumble sequence: " + ex);
|
||||
Debug.WriteLine("CGamePad: Failed to stop vibration: " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void _ResetInputState()
|
||||
{
|
||||
_OldState = new GamePadState();
|
||||
foreach (var input in _RepeatingInputs)
|
||||
{
|
||||
input.Reset();
|
||||
}
|
||||
|
||||
return true;
|
||||
_MouseTimer.Reset();
|
||||
}
|
||||
|
||||
private void _HandleInput(GamePadState state)
|
||||
{
|
||||
_PendingKeys.Clear();
|
||||
|
||||
foreach (var input in _RepeatingInputs)
|
||||
{
|
||||
input.Update(state, _PendingKeys);
|
||||
}
|
||||
|
||||
if (_IsNewlyPressed(state.Buttons.Start, _OldState.Buttons.Start))
|
||||
{
|
||||
_PendingKeys.Add(Keys.Space);
|
||||
}
|
||||
|
||||
if (_IsNewlyPressed(state.Buttons.A, _OldState.Buttons.A))
|
||||
{
|
||||
_PendingKeys.Add(Keys.Enter);
|
||||
}
|
||||
|
||||
if (_IsNewlyPressed(state.Buttons.B, _OldState.Buttons.B))
|
||||
{
|
||||
_PendingKeys.Add(Keys.Escape);
|
||||
}
|
||||
|
||||
if (_IsNewlyPressed(state.Buttons.Back, _OldState.Buttons.Back))
|
||||
{
|
||||
_PendingKeys.Add(Keys.Back);
|
||||
}
|
||||
|
||||
foreach (var key in _PendingKeys)
|
||||
{
|
||||
AddKeyEvent(new SKeyEvent(ESender.Gamepad, false, false, false, false, char.MinValue, key));
|
||||
}
|
||||
|
||||
_HandleMouse(state);
|
||||
|
||||
_OldState = state;
|
||||
}
|
||||
|
||||
private void _HandleMouse(GamePadState state)
|
||||
{
|
||||
var leftClick =
|
||||
_IsNewlyPressed(state.Buttons.LeftShoulder, _OldState.Buttons.LeftShoulder) ||
|
||||
_IsNewlyPressed(state.Buttons.RightStick, _OldState.Buttons.RightStick);
|
||||
var rightClick = _IsNewlyPressed(state.Buttons.RightShoulder, _OldState.Buttons.RightShoulder);
|
||||
|
||||
var elapsedSeconds = _MouseTimer.IsRunning ? Math.Min((float)_MouseTimer.Elapsed.TotalSeconds, MaxMouseStepSeconds) : 0.0f;
|
||||
_MouseTimer.Restart();
|
||||
|
||||
var x = state.ThumbSticks.Right.X;
|
||||
var y = state.ThumbSticks.Right.Y;
|
||||
_ApplyRadialDeadZone(ref x, ref y);
|
||||
|
||||
var oldX = (int)_MouseX;
|
||||
var oldY = (int)_MouseY;
|
||||
|
||||
var step = MouseSpeedPxPerSecond * elapsedSeconds;
|
||||
_MouseX = _Clamp(_MouseX + x * step, 0.0f, CSettings.RenderW - 1);
|
||||
_MouseY = _Clamp(_MouseY - y * step, 0.0f, CSettings.RenderH - 1);
|
||||
|
||||
var moved = (int)_MouseX != oldX || (int)_MouseY != oldY;
|
||||
if (!moved && !leftClick && !rightClick)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AddMouseEvent(new SMouseEvent(ESender.Gamepad, EModifier.None, (int)_MouseX, (int)_MouseY, leftClick, false, rightClick, 0, false, false, false, false));
|
||||
}
|
||||
|
||||
private static void _ApplyRadialDeadZone(ref float x, ref float y)
|
||||
{
|
||||
var length = (float)Math.Sqrt(x * x + y * y);
|
||||
if (length < RightStickDeadZone)
|
||||
{
|
||||
x = 0.0f;
|
||||
y = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
var scaledLength = (Math.Min(length, 1.0f) - RightStickDeadZone) / (1.0f - RightStickDeadZone);
|
||||
var factor = scaledLength / length;
|
||||
x *= factor;
|
||||
y *= factor;
|
||||
}
|
||||
|
||||
private static float _Clamp(float value, float min, float max)
|
||||
{
|
||||
return Math.Max(min, Math.Min(max, value));
|
||||
}
|
||||
|
||||
private static bool _IsNewlyPressed(ButtonState now, ButtonState old)
|
||||
{
|
||||
return now == ButtonState.Pressed && old == ButtonState.Released;
|
||||
}
|
||||
|
||||
private static bool _IsAxisPressed(float value, bool wasPressed)
|
||||
{
|
||||
return value > (wasPressed ? StickReleaseThreshold : StickPressThreshold);
|
||||
}
|
||||
|
||||
private static bool _IsTriggerPressed(float value, bool wasPressed)
|
||||
{
|
||||
return value >= (wasPressed ? TriggerReleaseThreshold : TriggerPressThreshold);
|
||||
}
|
||||
|
||||
private sealed class CRepeatingInput
|
||||
{
|
||||
private readonly Keys _Key;
|
||||
private readonly Func<GamePadState, bool, bool> _IsPressed;
|
||||
private readonly Stopwatch _Timer = new Stopwatch();
|
||||
private bool _Pressed;
|
||||
private bool _Repeating;
|
||||
|
||||
public CRepeatingInput(Keys key, Func<GamePadState, bool, bool> isPressed)
|
||||
{
|
||||
_Key = key;
|
||||
_IsPressed = isPressed;
|
||||
}
|
||||
|
||||
public void Update(GamePadState state, ICollection<Keys> keys)
|
||||
{
|
||||
if (!_IsPressed(state, _Pressed))
|
||||
{
|
||||
Reset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_Pressed)
|
||||
{
|
||||
keys.Add(_Key);
|
||||
_Pressed = true;
|
||||
_Repeating = false;
|
||||
_Timer.Restart();
|
||||
return;
|
||||
}
|
||||
|
||||
var delay = _Repeating ? KeyRepeatDelayMs : InitialRepeatDelayMs;
|
||||
if (_Timer.ElapsedMilliseconds >= delay)
|
||||
{
|
||||
keys.Add(_Key);
|
||||
_Repeating = true;
|
||||
_Timer.Restart();
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_Pressed = false;
|
||||
_Repeating = false;
|
||||
_Timer.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user