diff options
Diffstat (limited to 'Plugins/MonoGame.Extended/source/MonoGame.Extended.Input')
24 files changed, 1742 insertions, 0 deletions
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/ExtendedPlayerIndex.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/ExtendedPlayerIndex.cs new file mode 100644 index 0000000..a153f02 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/ExtendedPlayerIndex.cs @@ -0,0 +1,32 @@ +using Microsoft.Xna.Framework; + +namespace MonoGame.Extended.Input +{ + /// <summary>Player index enumeration with slots for 8 players</summary> + public enum ExtendedPlayerIndex + { + /// <summary>First player</summary> + One = PlayerIndex.One, + + /// <summary>Second player</summary> + Two = PlayerIndex.Two, + + /// <summary>Third player</summary> + Three = PlayerIndex.Three, + + /// <summary>Fourth player</summary> + Four = PlayerIndex.Four, + + /// <summary>Fifth player</summary> + Five, + + /// <summary>Sixth player</summary> + Six, + + /// <summary>Seventh player</summary> + Seven, + + /// <summary>Eigth player</summary> + Eight + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/GamePadEventArgs.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/GamePadEventArgs.cs new file mode 100644 index 0000000..9b113c6 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/GamePadEventArgs.cs @@ -0,0 +1,62 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; + +namespace MonoGame.Extended.Input.InputListeners +{ + /// <summary> + /// This class contains all information resulting from events fired by + /// <see cref="GamePadListener" />. + /// </summary> + public class GamePadEventArgs : EventArgs + { + public GamePadEventArgs(GamePadState previousState, GamePadState currentState, + TimeSpan elapsedTime, PlayerIndex playerIndex, Buttons? button = null, + float triggerState = 0, Vector2? thumbStickState = null) + { + PlayerIndex = playerIndex; + PreviousState = previousState; + CurrentState = currentState; + ElapsedTime = elapsedTime; + if (button != null) + Button = button.Value; + TriggerState = triggerState; + ThumbStickState = thumbStickState ?? Vector2.Zero; + } + + /// <summary> + /// The index of the controller. + /// </summary> + public PlayerIndex PlayerIndex { get; private set; } + + /// <summary> + /// The state of the controller in the previous update. + /// </summary> + public GamePadState PreviousState { get; private set; } + + /// <summary> + /// The state of the controller in this update. + /// </summary> + public GamePadState CurrentState { get; private set; } + + /// <summary> + /// The button that triggered this event, if appliable. + /// </summary> + public Buttons Button { get; private set; } + + /// <summary> + /// The time elapsed since last event. + /// </summary> + public TimeSpan ElapsedTime { get; private set; } + + /// <summary> + /// If a TriggerMoved event, displays the responsible trigger's position. + /// </summary> + public float TriggerState { get; private set; } + + /// <summary> + /// If a ThumbStickMoved event, displays the responsible stick's position. + /// </summary> + public Vector2 ThumbStickState { get; private set; } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/GamePadListener.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/GamePadListener.cs new file mode 100644 index 0000000..b7ea79b --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/GamePadListener.cs @@ -0,0 +1,529 @@ +using System; +using System.Linq; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; + +namespace MonoGame.Extended.Input.InputListeners +{ + /// <summary> + /// This is a listener that exposes several events for easier handling of gamepads. + /// </summary> + public class GamePadListener : InputListener + { + private static readonly bool[] _gamePadConnections = new bool[4]; + + // These buttons are not to be evaluated normally, but with the debounce filter + // in their respective methods. + private readonly Buttons[] _excludedButtons = + { + Buttons.LeftTrigger, Buttons.RightTrigger, + Buttons.LeftThumbstickDown, Buttons.LeftThumbstickUp, Buttons.LeftThumbstickRight, + Buttons.LeftThumbstickLeft, + Buttons.RightThumbstickLeft, Buttons.RightThumbstickRight, Buttons.RightThumbstickUp, + Buttons.RightThumbstickDown + }; + + private GamePadState _currentState; + //private int _lastPacketNumber; + // Implementation doesn't work, see explanation in CheckAllButtons(). + private GameTime _gameTime; + private Buttons _lastButton; + private Buttons _lastLeftStickDirection; + private Buttons _lastRightStickDirection; + private GamePadState _lastThumbStickState; + + private GamePadState _lastTriggerState; + + private float _leftCurVibrationStrength; + private bool _leftStickDown; + private bool _leftTriggerDown; + private bool _leftVibrating; + private GameTime _previousGameTime; + private GamePadState _previousState; + private int _repeatedButtonTimer; + private float _rightCurVibrationStrength; + private bool _rightStickDown; + private bool _rightTriggerDown; + private bool _rightVibrating; + private TimeSpan _vibrationDurationLeft; + private TimeSpan _vibrationDurationRight; + private TimeSpan _vibrationStart; + + private float _vibrationStrengthLeft; + private float _vibrationStrengthRight; + + public GamePadListener() + : this(new GamePadListenerSettings()) + { + } + + public GamePadListener(GamePadListenerSettings settings) + { + PlayerIndex = settings.PlayerIndex; + VibrationEnabled = settings.VibrationEnabled; + VibrationStrengthLeft = settings.VibrationStrengthLeft; + VibrationStrengthRight = settings.VibrationStrengthRight; + ThumbStickDeltaTreshold = settings.ThumbStickDeltaTreshold; + ThumbstickDownTreshold = settings.ThumbstickDownTreshold; + TriggerDeltaTreshold = settings.TriggerDeltaTreshold; + TriggerDownTreshold = settings.TriggerDownTreshold; + RepeatInitialDelay = settings.RepeatInitialDelay; + RepeatDelay = settings.RepeatDelay; + + _previousGameTime = new GameTime(); + _previousState = GamePadState.Default; + } + + /// <summary> + /// If set to true, the static event <see cref="ControllerConnectionChanged" /> + /// will fire when any controller changes in connectivity status. + /// <para> + /// This functionality requires that you have one actively updating + /// <see cref="InputListenerManager" />. + /// </para> + /// </summary> + public static bool CheckControllerConnections { get; set; } + + /// <summary> + /// The index of the controller. + /// </summary> + public PlayerIndex PlayerIndex { get; } + + /// <summary> + /// When a button is held down, the interval in which + /// ButtonRepeated fires. Value in milliseconds. + /// </summary> + public int RepeatDelay { get; } + + /// <summary> + /// The amount of time a button has to be held down + /// in order to fire ButtonRepeated the first time. + /// Value in milliseconds. + /// </summary> + public int RepeatInitialDelay { get; } + + /// <summary> + /// Whether vibration is enabled for this controller. + /// </summary> + public bool VibrationEnabled { get; set; } + + /// <summary> + /// General setting for the strength of the left motor. + /// This motor has a slow, deep, powerful rumble. + /// <para> + /// This setting will modify all future vibrations + /// through this listener. + /// </para> + /// </summary> + public float VibrationStrengthLeft + { + get { return _vibrationStrengthLeft; } + // Clamp the value, just to be sure. + set { _vibrationStrengthLeft = MathHelper.Clamp(value, 0, 1); } + } + + /// <summary> + /// General setting for the strength of the right motor. + /// This motor has a snappy, quick, high-pitched rumble. + /// <para> + /// This setting will modify all future vibrations + /// through this listener. + /// </para> + /// </summary> + public float VibrationStrengthRight + { + get { return _vibrationStrengthRight; } + // Clamp the value, just to be sure. + set { _vibrationStrengthRight = MathHelper.Clamp(value, 0, 1); } + } + + /// <summary> + /// The treshold of movement that has to be met in order + /// for the listener to fire an event with the trigger's + /// updated position. + /// <para> + /// In essence this defines the event's + /// resolution. + /// </para> + /// At a value of 0 this will fire every time + /// the trigger's position is not 0f. + /// </summary> + public float TriggerDeltaTreshold { get; } + + /// <summary> + /// The treshold of movement that has to be met in order + /// for the listener to fire an event with the thumbstick's + /// updated position. + /// <para> + /// In essence this defines the event's + /// resolution. + /// </para> + /// At a value of 0 this will fire every time + /// the thumbstick's position is not {x:0, y:0}. + /// </summary> + public float ThumbStickDeltaTreshold { get; } + + /// <summary> + /// How deep the triggers have to be depressed in order to + /// register as a ButtonDown event. + /// </summary> + public float TriggerDownTreshold { get; } + + /// <summary> + /// How deep the triggers have to be depressed in order to + /// register as a ButtonDown event. + /// </summary> + public float ThumbstickDownTreshold { get; } + + /// <summary> + /// This event fires whenever a controller connects or disconnects. + /// <para> + /// In order + /// for it to work, the <see cref="CheckControllerConnections" /> property must + /// be set to true. + /// </para> + /// </summary> + public static event EventHandler<GamePadEventArgs> ControllerConnectionChanged; + + /// <summary> + /// This event fires whenever a button changes from the Up + /// to the Down state. + /// </summary> + public event EventHandler<GamePadEventArgs> ButtonDown; + + /// <summary> + /// This event fires whenever a button changes from the Down + /// to the Up state. + /// </summary> + public event EventHandler<GamePadEventArgs> ButtonUp; + + /// <summary> + /// This event fires repeatedly whenever a button is held sufficiently + /// long. Use this for things like menu navigation. + /// </summary> + public event EventHandler<GamePadEventArgs> ButtonRepeated; + + /// <summary> + /// This event fires whenever a thumbstick changes position. + /// <para> + /// The parameter governing the sensitivity of this functionality + /// is <see cref="GamePadListenerSettings.ThumbStickDeltaTreshold" />. + /// </para> + /// </summary> + public event EventHandler<GamePadEventArgs> ThumbStickMoved; + + /// <summary> + /// This event fires whenever a trigger changes position. + /// <para> + /// The parameter governing the sensitivity of this functionality + /// is <see cref="GamePadListenerSettings.TriggerDeltaTreshold" />. + /// </para> + /// </summary> + public event EventHandler<GamePadEventArgs> TriggerMoved; + + + /// <summary> + /// Send a vibration command to the controller. + /// Returns true if the operation succeeded. + /// <para> + /// Motor values that are unset preserve + /// their current vibration strength and duration. + /// </para> + /// Note: Vibration currently only works on select platforms, + /// like Monogame.Windows. + /// </summary> + /// <param name="durationMs">Duration of the vibration in milliseconds.</param> + /// <param name="leftStrength"> + /// The strength of the left motor. + /// This motor has a slow, deep, powerful rumble. + /// </param> + /// <param name="rightStrength"> + /// The strength of the right motor. + /// This motor has a snappy, quick, high-pitched rumble. + /// </param> + /// <returns>Returns true if the operation succeeded.</returns> + public bool Vibrate(int durationMs, float leftStrength = float.NegativeInfinity, + float rightStrength = float.NegativeInfinity) + { + if (!VibrationEnabled) + return false; + + var lstrength = MathHelper.Clamp(leftStrength, 0, 1); + var rstrength = MathHelper.Clamp(rightStrength, 0, 1); + + if (float.IsNegativeInfinity(leftStrength)) + lstrength = _leftCurVibrationStrength; + if (float.IsNegativeInfinity(rightStrength)) + rstrength = _rightCurVibrationStrength; + + var success = GamePad.SetVibration(PlayerIndex, lstrength*VibrationStrengthLeft, + rstrength*VibrationStrengthRight); + if (success) + { + _leftVibrating = true; + _rightVibrating = true; + + if (leftStrength > 0) + _vibrationDurationLeft = new TimeSpan(0, 0, 0, 0, durationMs); + else + { + if (lstrength > 0) + _vibrationDurationLeft -= _gameTime.TotalGameTime - _vibrationStart; + else + _leftVibrating = false; + } + + if (rightStrength > 0) + _vibrationDurationRight = new TimeSpan(0, 0, 0, 0, durationMs); + else + { + if (rstrength > 0) + _vibrationDurationRight -= _gameTime.TotalGameTime - _vibrationStart; + else + _rightVibrating = false; + } + + _vibrationStart = _gameTime.TotalGameTime; + + _leftCurVibrationStrength = lstrength; + _rightCurVibrationStrength = rstrength; + } + return success; + } + + private void CheckAllButtons() + { + // PacketNumber only and always changes if there is a difference between GamePadStates. + // ...At least, that's the theory. It doesn't seem to be implemented. Disabled for now. + //if (_lastPacketNumber == _currentState.PacketNumber) + // return; + foreach (Buttons button in Enum.GetValues(typeof(Buttons))) + { + if (_excludedButtons.Contains(button)) + break; + if (_currentState.IsButtonDown(button) && _previousState.IsButtonUp(button)) + RaiseButtonDown(button); + if (_currentState.IsButtonUp(button) && _previousState.IsButtonDown(button)) + RaiseButtonUp(button); + } + + // Checks triggers as buttons and floats + CheckTriggers(s => s.Triggers.Left, Buttons.LeftTrigger); + CheckTriggers(s => s.Triggers.Right, Buttons.RightTrigger); + + // Checks thumbsticks as vector2s + CheckThumbSticks(s => s.ThumbSticks.Right, Buttons.RightStick); + CheckThumbSticks(s => s.ThumbSticks.Left, Buttons.LeftStick); + } + + private void CheckTriggers(Func<GamePadState, float> getButtonState, Buttons button) + { + var debounce = 0.05f; // Value used to qualify a trigger as coming Up from a Down state + var curstate = getButtonState(_currentState); + var curdown = curstate > TriggerDownTreshold; + var prevdown = button == Buttons.RightTrigger ? _rightTriggerDown : _leftTriggerDown; + + if (!prevdown && curdown) + { + RaiseButtonDown(button); + if (button == Buttons.RightTrigger) + _rightTriggerDown = true; + else + _leftTriggerDown = true; + } + else + { + if (prevdown && (curstate < debounce)) + { + RaiseButtonUp(button); + if (button == Buttons.RightTrigger) + _rightTriggerDown = false; + else + _leftTriggerDown = false; + } + } + + var prevstate = getButtonState(_lastTriggerState); + if (curstate > TriggerDeltaTreshold) + { + if (Math.Abs(prevstate - curstate) >= TriggerDeltaTreshold) + { + TriggerMoved?.Invoke(this, MakeArgs(button, curstate)); + _lastTriggerState = _currentState; + } + } + else + { + if (prevstate > TriggerDeltaTreshold) + { + TriggerMoved?.Invoke(this, MakeArgs(button, curstate)); + _lastTriggerState = _currentState; + } + } + } + + private void CheckThumbSticks(Func<GamePadState, Vector2> getButtonState, Buttons button) + { + const float debounce = 0.15f; + var curVector = getButtonState(_currentState); + var curdown = curVector.Length() > ThumbstickDownTreshold; + var right = button == Buttons.RightStick; + var prevdown = right ? _rightStickDown : _leftStickDown; + + var prevdir = button == Buttons.RightStick ? _lastRightStickDirection : _lastLeftStickDirection; + Buttons curdir; + if (curVector.Y > curVector.X) + { + if (curVector.Y > -curVector.X) + curdir = right ? Buttons.RightThumbstickUp : Buttons.LeftThumbstickUp; + else + curdir = right ? Buttons.RightThumbstickLeft : Buttons.LeftThumbstickLeft; + } + else + { + if (curVector.Y < -curVector.X) + curdir = right ? Buttons.RightThumbstickDown : Buttons.LeftThumbstickDown; + else + curdir = right ? Buttons.RightThumbstickRight : Buttons.LeftThumbstickRight; + } + + if (!prevdown && curdown) + { + if (right) + _lastRightStickDirection = curdir; + else + _lastLeftStickDirection = curdir; + + RaiseButtonDown(curdir); + if (button == Buttons.RightStick) + _rightStickDown = true; + else + _leftStickDown = true; + } + else + { + if (prevdown && (curVector.Length() < debounce)) + { + RaiseButtonUp(prevdir); + if (button == Buttons.RightStick) + _rightStickDown = false; + else + _leftStickDown = false; + } + else + { + if (prevdown && curdown && (curdir != prevdir)) + { + RaiseButtonUp(prevdir); + if (right) + _lastRightStickDirection = curdir; + else + _lastLeftStickDirection = curdir; + RaiseButtonDown(curdir); + } + } + } + + var prevVector = getButtonState(_lastThumbStickState); + if (curVector.Length() > ThumbStickDeltaTreshold) + { + if (Vector2.Distance(curVector, prevVector) >= ThumbStickDeltaTreshold) + { + ThumbStickMoved?.Invoke(this, MakeArgs(button, thumbStickState: curVector)); + _lastThumbStickState = _currentState; + } + } + else + { + if (prevVector.Length() > ThumbStickDeltaTreshold) + { + ThumbStickMoved?.Invoke(this, MakeArgs(button, thumbStickState: curVector)); + _lastThumbStickState = _currentState; + } + } + } + + internal static void CheckConnections() + { + if (!CheckControllerConnections) + return; + + foreach (PlayerIndex index in Enum.GetValues(typeof(PlayerIndex))) + { + if (GamePad.GetState(index).IsConnected ^ _gamePadConnections[(int) index]) + // We need more XORs in this world + { + _gamePadConnections[(int) index] = !_gamePadConnections[(int) index]; + ControllerConnectionChanged?.Invoke(null, + new GamePadEventArgs(GamePadState.Default, GamePad.GetState(index), TimeSpan.Zero, index)); + } + } + } + + private void CheckVibrate() + { + if (_leftVibrating && (_vibrationStart + _vibrationDurationLeft < _gameTime.TotalGameTime)) + Vibrate(0, 0); + if (_rightVibrating && (_vibrationStart + _vibrationDurationRight < _gameTime.TotalGameTime)) + Vibrate(0, rightStrength: 0); + } + + public override void Update(GameTime gameTime) + { + _gameTime = gameTime; + _currentState = GamePad.GetState(PlayerIndex); + CheckVibrate(); + if (!_currentState.IsConnected) + return; + CheckAllButtons(); + CheckRepeatButton(); + //_lastPacketNumber = _currentState.PacketNumber; + _previousGameTime = gameTime; + _previousState = _currentState; + } + + private GamePadEventArgs MakeArgs(Buttons? button, + float triggerstate = 0, Vector2? thumbStickState = null) + { + var elapsedTime = _gameTime.TotalGameTime - _previousGameTime.TotalGameTime; + return new GamePadEventArgs(_previousState, _currentState, + elapsedTime, PlayerIndex, button, triggerstate, thumbStickState); + } + + private void RaiseButtonDown(Buttons button) + { + ButtonDown?.Invoke(this, MakeArgs(button)); + ButtonRepeated?.Invoke(this, MakeArgs(button)); + _lastButton = button; + _repeatedButtonTimer = 0; + } + + private void RaiseButtonUp(Buttons button) + { + ButtonUp?.Invoke(this, MakeArgs(button)); + _lastButton = 0; + } + + private void CheckRepeatButton() + { + _repeatedButtonTimer += _gameTime.ElapsedGameTime.Milliseconds; + + if ((_repeatedButtonTimer < RepeatInitialDelay) || (_lastButton == 0)) + return; + + if (_repeatedButtonTimer < RepeatInitialDelay + RepeatDelay) + { + ButtonRepeated?.Invoke(this, MakeArgs(_lastButton)); + _repeatedButtonTimer = RepeatDelay + RepeatInitialDelay; + } + else + { + if (_repeatedButtonTimer > RepeatInitialDelay + RepeatDelay*2) + { + ButtonRepeated?.Invoke(this, MakeArgs(_lastButton)); + _repeatedButtonTimer = RepeatDelay + RepeatInitialDelay; + } + } + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/GamePadListenerSettings.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/GamePadListenerSettings.cs new file mode 100644 index 0000000..8c36e4c --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/GamePadListenerSettings.cs @@ -0,0 +1,134 @@ +using Microsoft.Xna.Framework; + +namespace MonoGame.Extended.Input.InputListeners +{ + /// <summary> + /// This is a class that contains settings to be used to initialise a <see cref="GamePadListener" />. + /// </summary> + /// <seealso cref="InputListenerManager" /> + public class GamePadListenerSettings : InputListenerSettings<GamePadListener> + { + public GamePadListenerSettings() + : this(PlayerIndex.One) + { + } + + /// <summary> + /// This is a class that contains settings to be used to initialise a <see cref="GamePadListener" />. + /// <para>Note: There are a number of extra settings that are settable properties.</para> + /// </summary> + /// <param name="playerIndex">The index of the controller the listener will be tied to.</param> + /// <param name="vibrationEnabled">Whether vibration is enabled on the controller.</param> + /// <param name="vibrationStrengthLeft"> + /// General setting for the strength of the left motor. + /// This motor has a slow, deep, powerful rumble. + /// This setting will modify all future vibrations + /// through this listener. + /// </param> + /// <param name="vibrationStrengthRight"> + /// General setting for the strength of the right motor. + /// This motor has a snappy, quick, high-pitched rumble. + /// This setting will modify all future vibrations + /// through this listener. + /// </param> + public GamePadListenerSettings(PlayerIndex playerIndex, bool vibrationEnabled = true, + float vibrationStrengthLeft = 1.0f, float vibrationStrengthRight = 1.0f) + { + PlayerIndex = playerIndex; + VibrationEnabled = vibrationEnabled; + VibrationStrengthLeft = vibrationStrengthLeft; + VibrationStrengthRight = vibrationStrengthRight; + TriggerDownTreshold = 0.15f; + ThumbstickDownTreshold = 0.5f; + RepeatInitialDelay = 500; + RepeatDelay = 50; + } + + /// <summary> + /// The index of the controller. + /// </summary> + public PlayerIndex PlayerIndex { get; set; } + + /// <summary> + /// When a button is held down, the interval in which + /// ButtonRepeated fires. Value in milliseconds. + /// </summary> + public int RepeatDelay { get; set; } + + /// <summary> + /// The amount of time a button has to be held down + /// in order to fire ButtonRepeated the first time. + /// Value in milliseconds. + /// </summary> + public int RepeatInitialDelay { get; set; } + + + /// <summary> + /// Whether vibration is enabled for this controller. + /// </summary> + public bool VibrationEnabled { get; set; } + + /// <summary> + /// General setting for the strength of the left motor. + /// This motor has a slow, deep, powerful rumble. + /// <para> + /// This setting will modify all future vibrations + /// through this listener. + /// </para> + /// </summary> + public float VibrationStrengthLeft { get; set; } + + /// <summary> + /// General setting for the strength of the right motor. + /// This motor has a snappy, quick, high-pitched rumble. + /// <para> + /// This setting will modify all future vibrations + /// through this listener. + /// </para> + /// </summary> + public float VibrationStrengthRight { get; set; } + + /// <summary> + /// The treshold of movement that has to be met in order + /// for the listener to fire an event with the trigger's + /// updated position. + /// <para> + /// In essence this defines the event's + /// resolution. + /// </para> + /// At a value of 0 this will fire every time + /// the trigger's position is not 0f. + /// </summary> + public float TriggerDeltaTreshold { get; set; } + + /// <summary> + /// The treshold of movement that has to be met in order + /// for the listener to fire an event with the thumbstick's + /// updated position. + /// <para> + /// In essence this defines the event's + /// resolution. + /// </para> + /// At a value of 0 this will fire every time + /// the thumbstick's position is not {x:0, y:0}. + /// </summary> + public float ThumbStickDeltaTreshold { get; set; } + + /// <summary> + /// How deep the triggers have to be depressed in order to + /// register as a ButtonDown event. + /// </summary> + public float TriggerDownTreshold { get; set; } + + /// <summary> + /// How deep the triggers have to be depressed in order to + /// register as a ButtonDown event. + /// </summary> + public float ThumbstickDownTreshold { get; private set; } + + public override GamePadListener CreateListener() + { + return new GamePadListener(this); + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/IInputService.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/IInputService.cs new file mode 100644 index 0000000..46198ec --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/IInputService.cs @@ -0,0 +1,13 @@ +namespace MonoGame.Extended.Input.InputListeners +{ + public interface IInputService + { + KeyboardListener GuiKeyboardListener { get; } + + MouseListener GuiMouseListener { get; } + + GamePadListener GuiGamePadListener { get; } + + TouchListener GuiTouchListener { get; } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/InputListener.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/InputListener.cs new file mode 100644 index 0000000..6323295 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/InputListener.cs @@ -0,0 +1,13 @@ +using Microsoft.Xna.Framework; + +namespace MonoGame.Extended.Input.InputListeners +{ + public abstract class InputListener + { + protected InputListener() + { + } + + public abstract void Update(GameTime gameTime); + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/InputListenerComponent.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/InputListenerComponent.cs new file mode 100644 index 0000000..302f6b5 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/InputListenerComponent.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using Microsoft.Xna.Framework; + +namespace MonoGame.Extended.Input.InputListeners +{ + public class InputListenerComponent : GameComponent, IUpdate + { + private readonly List<InputListener> _listeners; + + public InputListenerComponent(Game game) + : base(game) + { + _listeners = new List<InputListener>(); + } + + public InputListenerComponent(Game game, params InputListener[] listeners) + : base(game) + { + _listeners = new List<InputListener>(listeners); + } + + public IList<InputListener> Listeners => _listeners; + + public override void Update(GameTime gameTime) + { + base.Update(gameTime); + + if (Game.IsActive) + { + foreach (var listener in _listeners) + listener.Update(gameTime); + } + + GamePadListener.CheckConnections(); + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/InputListenerSettings.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/InputListenerSettings.cs new file mode 100644 index 0000000..468a30b --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/InputListenerSettings.cs @@ -0,0 +1,8 @@ +namespace MonoGame.Extended.Input.InputListeners +{ + public abstract class InputListenerSettings<T> + where T : InputListener + { + public abstract T CreateListener(); + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/KeyboardEventArgs.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/KeyboardEventArgs.cs new file mode 100644 index 0000000..d6d01ab --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/KeyboardEventArgs.cs @@ -0,0 +1,119 @@ +using System; +using Microsoft.Xna.Framework.Input; + +namespace MonoGame.Extended.Input.InputListeners +{ + public class KeyboardEventArgs : EventArgs + { + public KeyboardEventArgs(Keys key, KeyboardState keyboardState) + { + Key = key; + + Modifiers = KeyboardModifiers.None; + + if (keyboardState.IsKeyDown(Keys.LeftControl) || keyboardState.IsKeyDown(Keys.RightControl)) + Modifiers |= KeyboardModifiers.Control; + + if (keyboardState.IsKeyDown(Keys.LeftShift) || keyboardState.IsKeyDown(Keys.RightShift)) + Modifiers |= KeyboardModifiers.Shift; + + if (keyboardState.IsKeyDown(Keys.LeftAlt) || keyboardState.IsKeyDown(Keys.RightAlt)) + Modifiers |= KeyboardModifiers.Alt; + } + + public Keys Key { get; } + public KeyboardModifiers Modifiers { get; } + + public char? Character => ToChar(Key, Modifiers); + + private static char? ToChar(Keys key, KeyboardModifiers modifiers = KeyboardModifiers.None) + { + var isShiftDown = (modifiers & KeyboardModifiers.Shift) == KeyboardModifiers.Shift; + + if (key == Keys.A) return isShiftDown ? 'A' : 'a'; + if (key == Keys.B) return isShiftDown ? 'B' : 'b'; + if (key == Keys.C) return isShiftDown ? 'C' : 'c'; + if (key == Keys.D) return isShiftDown ? 'D' : 'd'; + if (key == Keys.E) return isShiftDown ? 'E' : 'e'; + if (key == Keys.F) return isShiftDown ? 'F' : 'f'; + if (key == Keys.G) return isShiftDown ? 'G' : 'g'; + if (key == Keys.H) return isShiftDown ? 'H' : 'h'; + if (key == Keys.I) return isShiftDown ? 'I' : 'i'; + if (key == Keys.J) return isShiftDown ? 'J' : 'j'; + if (key == Keys.K) return isShiftDown ? 'K' : 'k'; + if (key == Keys.L) return isShiftDown ? 'L' : 'l'; + if (key == Keys.M) return isShiftDown ? 'M' : 'm'; + if (key == Keys.N) return isShiftDown ? 'N' : 'n'; + if (key == Keys.O) return isShiftDown ? 'O' : 'o'; + if (key == Keys.P) return isShiftDown ? 'P' : 'p'; + if (key == Keys.Q) return isShiftDown ? 'Q' : 'q'; + if (key == Keys.R) return isShiftDown ? 'R' : 'r'; + if (key == Keys.S) return isShiftDown ? 'S' : 's'; + if (key == Keys.T) return isShiftDown ? 'T' : 't'; + if (key == Keys.U) return isShiftDown ? 'U' : 'u'; + if (key == Keys.V) return isShiftDown ? 'V' : 'v'; + if (key == Keys.W) return isShiftDown ? 'W' : 'w'; + if (key == Keys.X) return isShiftDown ? 'X' : 'x'; + if (key == Keys.Y) return isShiftDown ? 'Y' : 'y'; + if (key == Keys.Z) return isShiftDown ? 'Z' : 'z'; + + if (((key == Keys.D0) && !isShiftDown) || (key == Keys.NumPad0)) return '0'; + if (((key == Keys.D1) && !isShiftDown) || (key == Keys.NumPad1)) return '1'; + if (((key == Keys.D2) && !isShiftDown) || (key == Keys.NumPad2)) return '2'; + if (((key == Keys.D3) && !isShiftDown) || (key == Keys.NumPad3)) return '3'; + if (((key == Keys.D4) && !isShiftDown) || (key == Keys.NumPad4)) return '4'; + if (((key == Keys.D5) && !isShiftDown) || (key == Keys.NumPad5)) return '5'; + if (((key == Keys.D6) && !isShiftDown) || (key == Keys.NumPad6)) return '6'; + if (((key == Keys.D7) && !isShiftDown) || (key == Keys.NumPad7)) return '7'; + if (((key == Keys.D8) && !isShiftDown) || (key == Keys.NumPad8)) return '8'; + if (((key == Keys.D9) && !isShiftDown) || (key == Keys.NumPad9)) return '9'; + + if ((key == Keys.D0) && isShiftDown) return ')'; + if ((key == Keys.D1) && isShiftDown) return '!'; + if ((key == Keys.D2) && isShiftDown) return '@'; + if ((key == Keys.D3) && isShiftDown) return '#'; + if ((key == Keys.D4) && isShiftDown) return '$'; + if ((key == Keys.D5) && isShiftDown) return '%'; + if ((key == Keys.D6) && isShiftDown) return '^'; + if ((key == Keys.D7) && isShiftDown) return '&'; + if ((key == Keys.D8) && isShiftDown) return '*'; + if ((key == Keys.D9) && isShiftDown) return '('; + + if (key == Keys.Space) return ' '; + if (key == Keys.Tab) return '\t'; + if (key == Keys.Enter) return (char) 13; + if (key == Keys.Back) return (char) 8; + + if (key == Keys.Add) return '+'; + if (key == Keys.Decimal) return '.'; + if (key == Keys.Divide) return '/'; + if (key == Keys.Multiply) return '*'; + if (key == Keys.OemBackslash) return '\\'; + if ((key == Keys.OemComma) && !isShiftDown) return ','; + if ((key == Keys.OemComma) && isShiftDown) return '<'; + if ((key == Keys.OemOpenBrackets) && !isShiftDown) return '['; + if ((key == Keys.OemOpenBrackets) && isShiftDown) return '{'; + if ((key == Keys.OemCloseBrackets) && !isShiftDown) return ']'; + if ((key == Keys.OemCloseBrackets) && isShiftDown) return '}'; + if ((key == Keys.OemPeriod) && !isShiftDown) return '.'; + if ((key == Keys.OemPeriod) && isShiftDown) return '>'; + if ((key == Keys.OemPipe) && !isShiftDown) return '\\'; + if ((key == Keys.OemPipe) && isShiftDown) return '|'; + if ((key == Keys.OemPlus) && !isShiftDown) return '='; + if ((key == Keys.OemPlus) && isShiftDown) return '+'; + if ((key == Keys.OemMinus) && !isShiftDown) return '-'; + if ((key == Keys.OemMinus) && isShiftDown) return '_'; + if ((key == Keys.OemQuestion) && !isShiftDown) return '/'; + if ((key == Keys.OemQuestion) && isShiftDown) return '?'; + if ((key == Keys.OemQuotes) && !isShiftDown) return '\''; + if ((key == Keys.OemQuotes) && isShiftDown) return '"'; + if ((key == Keys.OemSemicolon) && !isShiftDown) return ';'; + if ((key == Keys.OemSemicolon) && isShiftDown) return ':'; + if ((key == Keys.OemTilde) && !isShiftDown) return '`'; + if ((key == Keys.OemTilde) && isShiftDown) return '~'; + if (key == Keys.Subtract) return '-'; + + return null; + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/KeyboardListener.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/KeyboardListener.cs new file mode 100644 index 0000000..9fc85a0 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/KeyboardListener.cs @@ -0,0 +1,104 @@ +using System; +using System.Linq; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; + +namespace MonoGame.Extended.Input.InputListeners +{ + public class KeyboardListener : InputListener + { + private Array _keysValues = Enum.GetValues(typeof(Keys)); + + private bool _isInitial; + private TimeSpan _lastPressTime; + + private Keys _previousKey; + private KeyboardState _previousState; + + public KeyboardListener() + : this(new KeyboardListenerSettings()) + { + } + + public KeyboardListener(KeyboardListenerSettings settings) + { + RepeatPress = settings.RepeatPress; + InitialDelay = settings.InitialDelayMilliseconds; + RepeatDelay = settings.RepeatDelayMilliseconds; + } + + public bool RepeatPress { get; } + public int InitialDelay { get; } + public int RepeatDelay { get; } + + public event EventHandler<KeyboardEventArgs> KeyTyped; + public event EventHandler<KeyboardEventArgs> KeyPressed; + public event EventHandler<KeyboardEventArgs> KeyReleased; + + public override void Update(GameTime gameTime) + { + var currentState = Keyboard.GetState(); + + RaisePressedEvents(gameTime, currentState); + RaiseReleasedEvents(currentState); + + if (RepeatPress) + RaiseRepeatEvents(gameTime, currentState); + + _previousState = currentState; + } + + private void RaisePressedEvents(GameTime gameTime, KeyboardState currentState) + { + if (!currentState.IsKeyDown(Keys.LeftAlt) && !currentState.IsKeyDown(Keys.RightAlt)) + { + var pressedKeys = _keysValues + .Cast<Keys>() + .Where(key => currentState.IsKeyDown(key) && _previousState.IsKeyUp(key)); + + foreach (var key in pressedKeys) + { + var args = new KeyboardEventArgs(key, currentState); + + KeyPressed?.Invoke(this, args); + + if (args.Character.HasValue) + KeyTyped?.Invoke(this, args); + + _previousKey = key; + _lastPressTime = gameTime.TotalGameTime; + _isInitial = true; + } + } + } + + private void RaiseReleasedEvents(KeyboardState currentState) + { + var releasedKeys = _keysValues + .Cast<Keys>() + .Where(key => currentState.IsKeyUp(key) && _previousState.IsKeyDown(key)); + + foreach (var key in releasedKeys) + KeyReleased?.Invoke(this, new KeyboardEventArgs(key, currentState)); + } + + private void RaiseRepeatEvents(GameTime gameTime, KeyboardState currentState) + { + var elapsedTime = (gameTime.TotalGameTime - _lastPressTime).TotalMilliseconds; + + if (currentState.IsKeyDown(_previousKey) && + (_isInitial && elapsedTime > InitialDelay || !_isInitial && elapsedTime > RepeatDelay)) + { + var args = new KeyboardEventArgs(_previousKey, currentState); + + KeyPressed?.Invoke(this, args); + + if (args.Character.HasValue) + KeyTyped?.Invoke(this, args); + + _lastPressTime = gameTime.TotalGameTime; + _isInitial = false; + } + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/KeyboardListenerSettings.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/KeyboardListenerSettings.cs new file mode 100644 index 0000000..86481ed --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/KeyboardListenerSettings.cs @@ -0,0 +1,21 @@ +namespace MonoGame.Extended.Input.InputListeners +{ + public class KeyboardListenerSettings : InputListenerSettings<KeyboardListener> + { + public KeyboardListenerSettings() + { + RepeatPress = true; + InitialDelayMilliseconds = 800; + RepeatDelayMilliseconds = 50; + } + + public bool RepeatPress { get; set; } + public int InitialDelayMilliseconds { get; set; } + public int RepeatDelayMilliseconds { get; set; } + + public override KeyboardListener CreateListener() + { + return new KeyboardListener(this); + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/KeyboardModifiers.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/KeyboardModifiers.cs new file mode 100644 index 0000000..de59905 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/KeyboardModifiers.cs @@ -0,0 +1,13 @@ +using System; + +namespace MonoGame.Extended.Input.InputListeners +{ + [Flags] + public enum KeyboardModifiers + { + Control = 1, + Shift = 2, + Alt = 4, + None = 0 + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/MouseEventArgs.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/MouseEventArgs.cs new file mode 100644 index 0000000..2717325 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/MouseEventArgs.cs @@ -0,0 +1,35 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; +using MonoGame.Extended.ViewportAdapters; + +namespace MonoGame.Extended.Input.InputListeners +{ + public class MouseEventArgs : EventArgs + { + public MouseEventArgs(ViewportAdapter viewportAdapter, TimeSpan time, MouseState previousState, + MouseState currentState, + MouseButton button = MouseButton.None) + { + PreviousState = previousState; + CurrentState = currentState; + Position = viewportAdapter?.PointToScreen(currentState.X, currentState.Y) + ?? new Point(currentState.X, currentState.Y); + Button = button; + ScrollWheelValue = currentState.ScrollWheelValue; + ScrollWheelDelta = currentState.ScrollWheelValue - previousState.ScrollWheelValue; + Time = time; + } + + public TimeSpan Time { get; } + + public MouseState PreviousState { get; } + public MouseState CurrentState { get; } + public Point Position { get; } + public MouseButton Button { get; } + public int ScrollWheelValue { get; } + public int ScrollWheelDelta { get; } + + public Vector2 DistanceMoved => CurrentState.Position.ToVector2() - PreviousState.Position.ToVector2(); + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/MouseListener.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/MouseListener.cs new file mode 100644 index 0000000..e71a67f --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/MouseListener.cs @@ -0,0 +1,193 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; +using MonoGame.Extended.ViewportAdapters; + +namespace MonoGame.Extended.Input.InputListeners +{ + /// <summary> + /// Handles mouse input. + /// </summary> + /// <remarks> + /// Due to nature of the listener, even when game is not in focus, listener will continue to be updated. + /// To avoid that, manual pause of Update() method is required whenever game loses focus. + /// To avoid having to do it manually, register listener to <see cref="InputListenerComponent" /> + /// </remarks> + public class MouseListener : InputListener + { + private MouseState _currentState; + private bool _dragging; + private GameTime _gameTime; + private bool _hasDoubleClicked; + private MouseEventArgs _mouseDownArgs; + private MouseEventArgs _previousClickArgs; + private MouseState _previousState; + + public MouseListener() + : this(new MouseListenerSettings()) + { + } + + public MouseListener(ViewportAdapter viewportAdapter) + : this(new MouseListenerSettings()) + { + ViewportAdapter = viewportAdapter; + } + + public MouseListener(MouseListenerSettings settings) + { + ViewportAdapter = settings.ViewportAdapter; + DoubleClickMilliseconds = settings.DoubleClickMilliseconds; + DragThreshold = settings.DragThreshold; + } + + public ViewportAdapter ViewportAdapter { get; } + + public int DoubleClickMilliseconds { get; } + public int DragThreshold { get; } + + /// <summary> + /// Returns true if the mouse has moved between the current and previous frames. + /// </summary> + /// <value><c>true</c> if the mouse has moved; otherwise, <c>false</c>.</value> + public bool HasMouseMoved => (_previousState.X != _currentState.X) || (_previousState.Y != _currentState.Y); + + public event EventHandler<MouseEventArgs> MouseDown; + public event EventHandler<MouseEventArgs> MouseUp; + public event EventHandler<MouseEventArgs> MouseClicked; + public event EventHandler<MouseEventArgs> MouseDoubleClicked; + public event EventHandler<MouseEventArgs> MouseMoved; + public event EventHandler<MouseEventArgs> MouseWheelMoved; + public event EventHandler<MouseEventArgs> MouseDragStart; + public event EventHandler<MouseEventArgs> MouseDrag; + public event EventHandler<MouseEventArgs> MouseDragEnd; + + private void CheckButtonPressed(Func<MouseState, ButtonState> getButtonState, MouseButton button) + { + if ((getButtonState(_currentState) == ButtonState.Pressed) && + (getButtonState(_previousState) == ButtonState.Released)) + { + var args = new MouseEventArgs(ViewportAdapter, _gameTime.TotalGameTime, _previousState, _currentState, button); + + MouseDown?.Invoke(this, args); + _mouseDownArgs = args; + + if (_previousClickArgs != null) + { + // If the last click was recent + var clickMilliseconds = (args.Time - _previousClickArgs.Time).TotalMilliseconds; + + if (clickMilliseconds <= DoubleClickMilliseconds) + { + MouseDoubleClicked?.Invoke(this, args); + _hasDoubleClicked = true; + } + + _previousClickArgs = null; + } + } + } + + private void CheckButtonReleased(Func<MouseState, ButtonState> getButtonState, MouseButton button) + { + if ((getButtonState(_currentState) == ButtonState.Released) && + (getButtonState(_previousState) == ButtonState.Pressed)) + { + var args = new MouseEventArgs(ViewportAdapter, _gameTime.TotalGameTime, _previousState, _currentState, button); + + if (_mouseDownArgs.Button == args.Button) + { + var clickMovement = DistanceBetween(args.Position, _mouseDownArgs.Position); + + // If the mouse hasn't moved much between mouse down and mouse up + if (clickMovement < DragThreshold) + { + if (!_hasDoubleClicked) + MouseClicked?.Invoke(this, args); + } + else // If the mouse has moved between mouse down and mouse up + { + MouseDragEnd?.Invoke(this, args); + _dragging = false; + } + } + + MouseUp?.Invoke(this, args); + + _hasDoubleClicked = false; + _previousClickArgs = args; + } + } + + private void CheckMouseDragged(Func<MouseState, ButtonState> getButtonState, MouseButton button) + { + if ((getButtonState(_currentState) == ButtonState.Pressed) && + (getButtonState(_previousState) == ButtonState.Pressed)) + { + var args = new MouseEventArgs(ViewportAdapter, _gameTime.TotalGameTime, _previousState, _currentState, button); + + if (_mouseDownArgs.Button == args.Button) + { + if (_dragging) + MouseDrag?.Invoke(this, args); + else + { + // Only start to drag based on DragThreshold + var clickMovement = DistanceBetween(args.Position, _mouseDownArgs.Position); + + if (clickMovement > DragThreshold) + { + _dragging = true; + MouseDragStart?.Invoke(this, args); + } + } + } + } + } + + public override void Update(GameTime gameTime) + { + _gameTime = gameTime; + _currentState = Mouse.GetState(); + + CheckButtonPressed(s => s.LeftButton, MouseButton.Left); + CheckButtonPressed(s => s.MiddleButton, MouseButton.Middle); + CheckButtonPressed(s => s.RightButton, MouseButton.Right); + CheckButtonPressed(s => s.XButton1, MouseButton.XButton1); + CheckButtonPressed(s => s.XButton2, MouseButton.XButton2); + + CheckButtonReleased(s => s.LeftButton, MouseButton.Left); + CheckButtonReleased(s => s.MiddleButton, MouseButton.Middle); + CheckButtonReleased(s => s.RightButton, MouseButton.Right); + CheckButtonReleased(s => s.XButton1, MouseButton.XButton1); + CheckButtonReleased(s => s.XButton2, MouseButton.XButton2); + + // Check for any sort of mouse movement. + if (HasMouseMoved) + { + MouseMoved?.Invoke(this, + new MouseEventArgs(ViewportAdapter, gameTime.TotalGameTime, _previousState, _currentState)); + + CheckMouseDragged(s => s.LeftButton, MouseButton.Left); + CheckMouseDragged(s => s.MiddleButton, MouseButton.Middle); + CheckMouseDragged(s => s.RightButton, MouseButton.Right); + CheckMouseDragged(s => s.XButton1, MouseButton.XButton1); + CheckMouseDragged(s => s.XButton2, MouseButton.XButton2); + } + + // Handle mouse wheel events. + if (_previousState.ScrollWheelValue != _currentState.ScrollWheelValue) + { + MouseWheelMoved?.Invoke(this, + new MouseEventArgs(ViewportAdapter, gameTime.TotalGameTime, _previousState, _currentState)); + } + + _previousState = _currentState; + } + + private static int DistanceBetween(Point a, Point b) + { + return Math.Abs(a.X - b.X) + Math.Abs(a.Y - b.Y); + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/MouseListenerSettings.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/MouseListenerSettings.cs new file mode 100644 index 0000000..1c0ca3d --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/MouseListenerSettings.cs @@ -0,0 +1,23 @@ +using MonoGame.Extended.ViewportAdapters; + +namespace MonoGame.Extended.Input.InputListeners +{ + public class MouseListenerSettings : InputListenerSettings<MouseListener> + { + public MouseListenerSettings() + { + // initial values are windows defaults + DoubleClickMilliseconds = 500; + DragThreshold = 2; + } + + public int DragThreshold { get; set; } + public int DoubleClickMilliseconds { get; set; } + public ViewportAdapter ViewportAdapter { get; set; } + + public override MouseListener CreateListener() + { + return new MouseListener(this); + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/TouchEventArgs.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/TouchEventArgs.cs new file mode 100644 index 0000000..8172885 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/TouchEventArgs.cs @@ -0,0 +1,39 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input.Touch; +using MonoGame.Extended.ViewportAdapters; + +namespace MonoGame.Extended.Input.InputListeners +{ + public class TouchEventArgs : EventArgs + { + public TouchEventArgs(ViewportAdapter viewportAdapter, TimeSpan time, TouchLocation location) + { + ViewportAdapter = viewportAdapter; + RawTouchLocation = location; + Time = time; + Position = viewportAdapter?.PointToScreen((int)location.Position.X, (int)location.Position.Y) + ?? location.Position.ToPoint(); + } + + public ViewportAdapter ViewportAdapter { get; } + public TouchLocation RawTouchLocation { get; } + public TimeSpan Time { get; } + public Point Position { get; } + + public override bool Equals(object other) + { + var args = other as TouchEventArgs; + + if (args == null) + return false; + + return ReferenceEquals(this, args) || RawTouchLocation.Id.Equals(args.RawTouchLocation.Id); + } + + public override int GetHashCode() + { + return RawTouchLocation.Id.GetHashCode(); + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/TouchListener.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/TouchListener.cs new file mode 100644 index 0000000..2a89cc9 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/TouchListener.cs @@ -0,0 +1,57 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input.Touch; +using MonoGame.Extended.ViewportAdapters; + +namespace MonoGame.Extended.Input.InputListeners +{ + public class TouchListener : InputListener + { + public TouchListener() + : this(new TouchListenerSettings()) + { + } + + public TouchListener(ViewportAdapter viewportAdapter) + : this(new TouchListenerSettings()) + { + ViewportAdapter = viewportAdapter; + } + + public TouchListener(TouchListenerSettings settings) + { + ViewportAdapter = settings.ViewportAdapter; + } + + public ViewportAdapter ViewportAdapter { get; set; } + + public event EventHandler<TouchEventArgs> TouchStarted; + public event EventHandler<TouchEventArgs> TouchEnded; + public event EventHandler<TouchEventArgs> TouchMoved; + public event EventHandler<TouchEventArgs> TouchCancelled; + + public override void Update(GameTime gameTime) + { + var touchCollection = TouchPanel.GetState(); + + foreach (var touchLocation in touchCollection) + { + switch (touchLocation.State) + { + case TouchLocationState.Pressed: + TouchStarted?.Invoke(this, new TouchEventArgs(ViewportAdapter, gameTime.TotalGameTime, touchLocation)); + break; + case TouchLocationState.Moved: + TouchMoved?.Invoke(this, new TouchEventArgs(ViewportAdapter, gameTime.TotalGameTime, touchLocation)); + break; + case TouchLocationState.Released: + TouchEnded?.Invoke(this, new TouchEventArgs(ViewportAdapter, gameTime.TotalGameTime, touchLocation)); + break; + case TouchLocationState.Invalid: + TouchCancelled?.Invoke(this, new TouchEventArgs(ViewportAdapter, gameTime.TotalGameTime, touchLocation)); + break; + } + } + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/TouchListenerSettings.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/TouchListenerSettings.cs new file mode 100644 index 0000000..6d42b42 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/InputListeners/TouchListenerSettings.cs @@ -0,0 +1,18 @@ +using MonoGame.Extended.ViewportAdapters; + +namespace MonoGame.Extended.Input.InputListeners +{ + public class TouchListenerSettings : InputListenerSettings<TouchListener> + { + public TouchListenerSettings() + { + } + + public ViewportAdapter ViewportAdapter { get; set; } + + public override TouchListener CreateListener() + { + return new TouchListener(this); + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/KeyboardExtended.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/KeyboardExtended.cs new file mode 100644 index 0000000..0ed7c76 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/KeyboardExtended.cs @@ -0,0 +1,22 @@ +using Microsoft.Xna.Framework.Input; + +namespace MonoGame.Extended.Input +{ + public static class KeyboardExtended + { + // TODO: This global static state was a horrible idea. + private static KeyboardState _currentKeyboardState; + private static KeyboardState _previousKeyboardState; + + public static KeyboardStateExtended GetState() + { + return new KeyboardStateExtended(_currentKeyboardState, _previousKeyboardState); + } + + public static void Refresh() + { + _previousKeyboardState = _currentKeyboardState; + _currentKeyboardState = Keyboard.GetState(); + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/KeyboardStateExtended.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/KeyboardStateExtended.cs new file mode 100644 index 0000000..ee18677 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/KeyboardStateExtended.cs @@ -0,0 +1,60 @@ +using System; +using System.Linq; +using Microsoft.Xna.Framework.Input; + +namespace MonoGame.Extended.Input +{ + public struct KeyboardStateExtended + { + private KeyboardState _currentKeyboardState; + private KeyboardState _previousKeyboardState; + + public KeyboardStateExtended(KeyboardState currentKeyboardState, KeyboardState previousKeyboardState) + { + _currentKeyboardState = currentKeyboardState; + _previousKeyboardState = previousKeyboardState; + } + + public bool CapsLock => _currentKeyboardState.CapsLock; + public bool NumLock => _currentKeyboardState.NumLock; + public bool IsShiftDown() => _currentKeyboardState.IsKeyDown(Keys.LeftShift) || _currentKeyboardState.IsKeyDown(Keys.RightShift); + public bool IsControlDown() => _currentKeyboardState.IsKeyDown(Keys.LeftControl) || _currentKeyboardState.IsKeyDown(Keys.RightControl); + public bool IsAltDown() => _currentKeyboardState.IsKeyDown(Keys.LeftAlt) || _currentKeyboardState.IsKeyDown(Keys.RightAlt); + public bool IsKeyDown(Keys key) => _currentKeyboardState.IsKeyDown(key); + public bool IsKeyUp(Keys key) => _currentKeyboardState.IsKeyUp(key); + public Keys[] GetPressedKeys() => _currentKeyboardState.GetPressedKeys(); + public void GetPressedKeys(Keys[] keys) => _currentKeyboardState.GetPressedKeys(keys); + + /// <summary> + /// Gets whether the given key was down on the previous state, but is now up. + /// </summary> + /// <param name="key">The key to check.</param> + /// <returns>true if the key was released this state-change, otherwise false.</returns> + [Obsolete($"Deprecated in favor of {nameof(IsKeyReleased)}")] + public bool WasKeyJustDown(Keys key) => _previousKeyboardState.IsKeyDown(key) && _currentKeyboardState.IsKeyUp(key); + + /// <summary> + /// Gets whether the given key was up on the previous state, but is now down. + /// </summary> + /// <param name="key">The key to check.</param> + /// <returns>true if the key was pressed this state-change, otherwise false.</returns> + [Obsolete($"Deprecated in favor of {nameof(IsKeyPressed)}")] + public bool WasKeyJustUp(Keys key) => _previousKeyboardState.IsKeyUp(key) && _currentKeyboardState.IsKeyDown(key); + + /// <summary> + /// Gets whether the given key was down on the previous state, but is now up. + /// </summary> + /// <param name="key">The key to check.</param> + /// <returns>true if the key was released this state-change, otherwise false.</returns> + public readonly bool IsKeyReleased(Keys key) => _previousKeyboardState.IsKeyDown(key) && _currentKeyboardState.IsKeyUp(key); + + /// <summary> + /// Gets whether the given key was up on the previous state, but is now down. + /// </summary> + /// <param name="key">The key to check.</param> + /// <returns>true if the key was pressed this state-change, otherwise false.</returns> + public readonly bool IsKeyPressed(Keys key) => _previousKeyboardState.IsKeyUp(key) && _currentKeyboardState.IsKeyDown(key); + + public bool WasAnyKeyJustDown() => _previousKeyboardState.GetPressedKeyCount() > 0; + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/MonoGame.Extended.Input.csproj b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/MonoGame.Extended.Input.csproj new file mode 100644 index 0000000..4b3bd82 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/MonoGame.Extended.Input.csproj @@ -0,0 +1,12 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <Description>An event based input system to MonoGame more awesome.</Description> + <PackageTags>monogame input event based listeners</PackageTags> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="..\MonoGame.Extended\MonoGame.Extended.csproj" /> + </ItemGroup> + +</Project> diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/MouseButton.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/MouseButton.cs new file mode 100644 index 0000000..e4a00f8 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/MouseButton.cs @@ -0,0 +1,15 @@ +using System; + +namespace MonoGame.Extended.Input +{ + [Flags] + public enum MouseButton + { + None = 0, + Left = 1, + Middle = 2, + Right = 4, + XButton1 = 8, + XButton2 = 16 + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/MouseExtended.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/MouseExtended.cs new file mode 100644 index 0000000..61d6d18 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/MouseExtended.cs @@ -0,0 +1,34 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; + +namespace MonoGame.Extended.Input +{ + public static class MouseExtended + { + // TODO: This global static state was a horrible idea. + private static MouseState _currentMouseState; + private static MouseState _previousMouseState; + + public static MouseStateExtended GetState() + { + return new MouseStateExtended(_currentMouseState, _previousMouseState); + } + + public static void Refresh() + { + _previousMouseState = _currentMouseState; + _currentMouseState = Mouse.GetState(); + } + + public static void SetPosition(int x, int y) => Mouse.SetPosition(x, y); + public static void SetPosition(Point point) => Mouse.SetPosition(point.X, point.Y); + public static void SetCursor(MouseCursor cursor) => Mouse.SetCursor(cursor); + + public static IntPtr WindowHandle + { + get => Mouse.WindowHandle; + set => Mouse.WindowHandle = value; + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/MouseStateExtended.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/MouseStateExtended.cs new file mode 100644 index 0000000..0ec6943 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Input/MouseStateExtended.cs @@ -0,0 +1,149 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; + +namespace MonoGame.Extended.Input +{ + public struct MouseStateExtended + { + private readonly MouseState _currentMouseState; + private readonly MouseState _previousMouseState; + + public MouseStateExtended(MouseState currentMouseState, MouseState previousMouseState) + { + _currentMouseState = currentMouseState; + _previousMouseState = previousMouseState; + } + + public int X => _currentMouseState.X; + public int Y => _currentMouseState.Y; + public Point Position => _currentMouseState.Position; + public bool PositionChanged => _currentMouseState.Position != _previousMouseState.Position; + + public int DeltaX => _previousMouseState.X - _currentMouseState.X; + public int DeltaY => _previousMouseState.Y - _currentMouseState.Y; + public Point DeltaPosition => new Point(DeltaX, DeltaY); + + public int ScrollWheelValue => _currentMouseState.ScrollWheelValue; + public int DeltaScrollWheelValue => _previousMouseState.ScrollWheelValue - _currentMouseState.ScrollWheelValue; + + public ButtonState LeftButton => _currentMouseState.LeftButton; + public ButtonState MiddleButton => _currentMouseState.MiddleButton; + public ButtonState RightButton => _currentMouseState.RightButton; + public ButtonState XButton1 => _currentMouseState.XButton1; + public ButtonState XButton2 => _currentMouseState.XButton2; + + public bool IsButtonDown(MouseButton button) + { + // ReSharper disable once SwitchStatementMissingSomeCases + switch (button) + { + case MouseButton.Left: return IsPressed(m => m.LeftButton); + case MouseButton.Middle: return IsPressed(m => m.MiddleButton); + case MouseButton.Right: return IsPressed(m => m.RightButton); + case MouseButton.XButton1: return IsPressed(m => m.XButton1); + case MouseButton.XButton2: return IsPressed(m => m.XButton2); + } + + return false; + } + + public bool IsButtonUp(MouseButton button) + { + // ReSharper disable once SwitchStatementMissingSomeCases + switch (button) + { + case MouseButton.Left: return IsReleased(m => m.LeftButton); + case MouseButton.Middle: return IsReleased(m => m.MiddleButton); + case MouseButton.Right: return IsReleased(m => m.RightButton); + case MouseButton.XButton1: return IsReleased(m => m.XButton1); + case MouseButton.XButton2: return IsReleased(m => m.XButton2); + } + + return false; + } + + /// <summary> + /// Get the just-down state for the mouse on this state-change: true if the mouse button has just been pressed. + /// </summary> + /// <param name="button"></param> + /// <remarks>Deprecated because of inconsistency with <see cref="KeyboardStateExtended"/></remarks> + /// <returns>The just-down state for the mouse on this state-change.</returns> + [Obsolete($"Deprecated in favor of {nameof(IsButtonPressed)}")] + public bool WasButtonJustDown(MouseButton button) + { + // ReSharper disable once SwitchStatementMissingSomeCases + switch (button) + { + case MouseButton.Left: return WasJustPressed(m => m.LeftButton); + case MouseButton.Middle: return WasJustPressed(m => m.MiddleButton); + case MouseButton.Right: return WasJustPressed(m => m.RightButton); + case MouseButton.XButton1: return WasJustPressed(m => m.XButton1); + case MouseButton.XButton2: return WasJustPressed(m => m.XButton2); + } + + return false; + } + + /// <summary> + /// Get the just-up state for the mouse on this state-change: true if the mouse button has just been released. + /// </summary> + /// <param name="button"></param> + /// <remarks>Deprecated because of inconsistency with <see cref="KeyboardStateExtended"/></remarks> + /// <returns>The just-up state for the mouse on this state-change.</returns> + [Obsolete($"Deprecated in favor of {nameof(IsButtonReleased)}")] + public bool WasButtonJustUp(MouseButton button) + { + // ReSharper disable once SwitchStatementMissingSomeCases + switch (button) + { + case MouseButton.Left: return WasJustReleased(m => m.LeftButton); + case MouseButton.Middle: return WasJustReleased(m => m.MiddleButton); + case MouseButton.Right: return WasJustReleased(m => m.RightButton); + case MouseButton.XButton1: return WasJustReleased(m => m.XButton1); + case MouseButton.XButton2: return WasJustReleased(m => m.XButton2); + } + + return false; + } + + /// <summary> + /// Get the pressed state of a mouse button, for this state-change. + /// </summary> + /// <param name="button">The button to check.</param> + /// <returns>true if the given mouse button was pressed this state-change, otherwise false</returns> + public readonly bool IsButtonPressed(MouseButton button) => button switch + { + MouseButton.Left => WasJustPressed(m => m.LeftButton), + MouseButton.Middle => WasJustPressed(m => m.MiddleButton), + MouseButton.Right => WasJustPressed(m => m.RightButton), + MouseButton.XButton1 => WasJustPressed(m => m.XButton1), + MouseButton.XButton2 => WasJustPressed(m => m.XButton2), + _ => false, + }; + + /// <summary> + /// Get the released state of a mouse button, for this state-change. + /// </summary> + /// <param name="button">The button to check.</param> + /// <returns>true if the given mouse button was released this state-change, otherwise false</returns> + public readonly bool IsButtonReleased(MouseButton button) => button switch + { + MouseButton.Left => WasJustReleased(m => m.LeftButton), + MouseButton.Middle => WasJustReleased(m => m.MiddleButton), + MouseButton.Right => WasJustReleased(m => m.RightButton), + MouseButton.XButton1 => WasJustReleased(m => m.XButton1), + MouseButton.XButton2 => WasJustReleased(m => m.XButton2), + _ => false, + }; + + private readonly bool IsPressed(Func<MouseState, ButtonState> button) + => button(_currentMouseState) == ButtonState.Pressed; + private readonly bool IsReleased(Func<MouseState, ButtonState> button) + => button(_currentMouseState) == ButtonState.Released; + private readonly bool WasJustPressed(Func<MouseState, ButtonState> button) + => button(_previousMouseState) == ButtonState.Released && button(_currentMouseState) == ButtonState.Pressed; + private readonly bool WasJustReleased(Func<MouseState, ButtonState> button) + => button(_previousMouseState) == ButtonState.Pressed && button(_currentMouseState) == ButtonState.Released; + } +} |