//-----------------------------------------------------------------------------
// InputState.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using System;
using System.Collections.Generic;
namespace CardsFramework
{
///
/// Helper for reading input from keyboard, gamepad, and touch input. This class
/// tracks both the current and previous state of the input devices, and implements
/// query methods for high level input actions such as "move up through the menu"
/// or "pause the game".
///
public class InputState
{
public const int MaxInputs = 4; // Maximum number of supported input devices (e.g., players)
// Current Inputstates - Tracks the latest state of all input devices
public readonly GamePadState[] CurrentGamePadStates;
public readonly KeyboardState[] CurrentKeyboardStates;
public MouseState CurrentMouseState;
private int touchCount; // Number of active touch inputs
public TouchCollection CurrentTouchState;
// Last Inputstates - Stores the previous frame's input states for detecting changes
public readonly GamePadState[] LastGamePadStates;
public readonly KeyboardState[] LastKeyboardStates;
public MouseState LastMouseState;
public TouchCollection LastTouchState;
public readonly List Gestures = new List(); // Stores touch gestures
public readonly bool[] GamePadWasConnected;
///
/// Cursor move speed in pixels per second
///
private const float cursorMoveSpeed = 250.0f;
private Vector2 currentCursorLocation;
///
/// Current location of our Cursor
///
public Vector2 CurrentCursorLocation => currentCursorLocation;
private Vector2 lastCursorLocation;
///
/// Current location of our Cursor
///
public Vector2 LastCursorLocation => lastCursorLocation;
private bool isMouseWheelScrolledDown;
///
/// Has the user scrolled the mouse wheel down?
///
public bool IsMouseWheelScrolledDown => isMouseWheelScrolledDown;
private bool isMouseWheelScrolledUp;
private Matrix inputTransformation; // Used to transform input coordinates between screen and game space
private float baseBufferWidth;
private float baseBufferHeight;
///
/// Has the user scrolled the mouse wheel up?
///
public bool IsMouseWheelScrolledUp => isMouseWheelScrolledUp;
///
/// Constructs a new input state.
///
public InputState(float baseBufferWidth, float baseBufferHeight)
{
this.baseBufferWidth = baseBufferWidth;
this.baseBufferHeight = baseBufferHeight;
// Initialize arrays for multiple controller/keyboard states
CurrentKeyboardStates = new KeyboardState[MaxInputs];
CurrentGamePadStates = new GamePadState[MaxInputs];
LastKeyboardStates = new KeyboardState[MaxInputs];
LastGamePadStates = new GamePadState[MaxInputs];
GamePadWasConnected = new bool[MaxInputs];
// Configure platform-specific input options
if (UIUtility.IsMobile)
{
TouchPanel.EnabledGestures = GestureType.Tap;
}
else if (UIUtility.IsDesktop)
{
// No desktop-specific initialization needed
}
else
{
// For now, we'll throw an exception if we don't know the platform
throw new PlatformNotSupportedException();
}
}
///
/// Reads the latest state of the keyboard and gamepad.
///
public void Update(GameTime gameTime)
{
// Update keyboard and gamepad states for all players
for (int i = 0; i < MaxInputs; i++)
{
LastKeyboardStates[i] = CurrentKeyboardStates[i];
LastGamePadStates[i] = CurrentGamePadStates[i];
CurrentKeyboardStates[i] = Keyboard.GetState();
CurrentGamePadStates[i] = GamePad.GetState((PlayerIndex)i);
// Keep track of whether a gamepad has ever been
// connected, so we can detect if it is unplugged.
if (CurrentGamePadStates[i].IsConnected)
{
GamePadWasConnected[i] = true;
}
}
// Update mouse state
LastMouseState = CurrentMouseState;
CurrentMouseState = Mouse.GetState();
// Update touch state
touchCount = 0;
LastTouchState = CurrentTouchState;
CurrentTouchState = TouchPanel.GetState();
// Process all available gestures
Gestures.Clear();
while (TouchPanel.IsGestureAvailable)
{
Gestures.Add(TouchPanel.ReadGesture());
}
// Process touch inputs
foreach (TouchLocation location in CurrentTouchState)
{
switch (location.State)
{
case TouchLocationState.Pressed:
touchCount++;
lastCursorLocation = currentCursorLocation;
// Transform touch position to game coordinates
currentCursorLocation = TransformCursorLocation(location.Position);
break;
case TouchLocationState.Moved:
break;
case TouchLocationState.Released:
break;
}
}
// Handle mouse clicks as touch equivalents
if (IsLeftMouseButtonClicked())
{
lastCursorLocation = currentCursorLocation;
// Transform mouse position to game coordinates
currentCursorLocation = TransformCursorLocation(new Vector2(CurrentMouseState.X, CurrentMouseState.Y));
touchCount = 1;
}
if (IsMiddleMouseButtonClicked())
{
touchCount = 2; // Treat middle mouse click as double touch
}
if (IsRightMoustButtonClicked())
{
touchCount = 3; // Treat right mouse click as triple touch
}
// Reset mouse wheel flags
isMouseWheelScrolledUp = false;
isMouseWheelScrolledDown = false;
// Detect mouse wheel scrolling
if (CurrentMouseState.ScrollWheelValue != LastMouseState.ScrollWheelValue)
{
int scrollWheelDelta = CurrentMouseState.ScrollWheelValue - LastMouseState.ScrollWheelValue;
// Handle the scroll wheel event based on the delta
if (scrollWheelDelta > 0)
{
// Mouse wheel scrolled up
isMouseWheelScrolledUp = true;
}
else if (scrollWheelDelta < 0)
{
// Mouse wheel scrolled down
isMouseWheelScrolledDown = true;
}
}
// Update the cursor location using gamepad and keyboard
float elapsedTime = (float)gameTime.ElapsedGameTime.TotalSeconds;
// Move cursor with 1st gamepad thumbstick
if (CurrentGamePadStates[0].IsConnected)
{
lastCursorLocation = currentCursorLocation;
currentCursorLocation.X += CurrentGamePadStates[0].ThumbSticks.Left.X * elapsedTime * cursorMoveSpeed;
currentCursorLocation.Y -= CurrentGamePadStates[0].ThumbSticks.Left.Y * elapsedTime * cursorMoveSpeed;
}
// Keep cursor within bounds
currentCursorLocation.X = MathHelper.Clamp(currentCursorLocation.X, 0f, baseBufferWidth);
currentCursorLocation.Y = MathHelper.Clamp(currentCursorLocation.Y, 0f, baseBufferHeight);
}
///
/// Checks if left mouse button was clicked (pressed and then released)
///
/// True if left mouse button was clicked, false otherwise.
internal bool IsLeftMouseButtonClicked()
{
return CurrentMouseState.LeftButton == ButtonState.Released && LastMouseState.LeftButton == ButtonState.Pressed;
}
///
/// Checks if middle mouse button was clicked (pressed and then released)
///
/// True if middle mouse button was clicked, false otherwise.
internal bool IsMiddleMouseButtonClicked()
{
return CurrentMouseState.MiddleButton == ButtonState.Released && LastMouseState.MiddleButton == ButtonState.Pressed;
}
///
/// Checks if right mouse button was clicked (pressed and then released)
///
/// True if right mouse button was clicked, false otherwise.
internal bool IsRightMoustButtonClicked()
{
return CurrentMouseState.RightButton == ButtonState.Released && LastMouseState.RightButton == ButtonState.Pressed;
}
///
/// Helper for checking if a key was newly pressed during this update. The
/// controllingPlayer parameter specifies which player to read input for.
/// If this is null, it will accept input from any player. When a keypress
/// is detected, the output playerIndex reports which player pressed it.
///
public bool IsNewKeyPress(Keys key, PlayerIndex? controllingPlayer,
out PlayerIndex playerIndex)
{
if (controllingPlayer.HasValue)
{
// Read input from the specified player.
playerIndex = controllingPlayer.Value;
int i = (int)playerIndex;
return (CurrentKeyboardStates[i].IsKeyDown(key) &&
LastKeyboardStates[i].IsKeyUp(key));
}
else
{
// Accept input from any player.
return (IsNewKeyPress(key, PlayerIndex.One, out playerIndex) ||
IsNewKeyPress(key, PlayerIndex.Two, out playerIndex) ||
IsNewKeyPress(key, PlayerIndex.Three, out playerIndex) ||
IsNewKeyPress(key, PlayerIndex.Four, out playerIndex));
}
}
///
/// Helper for checking if a button was newly pressed during this update.
/// The controllingPlayer parameter specifies which player to read input for.
/// If this is null, it will accept input from any player. When a button press
/// is detected, the output playerIndex reports which player pressed it.
///
public bool IsNewButtonPress(Buttons button, PlayerIndex? controllingPlayer,
out PlayerIndex playerIndex)
{
if (controllingPlayer.HasValue)
{
// Read input from the specified player.
playerIndex = controllingPlayer.Value;
int i = (int)playerIndex;
return (CurrentGamePadStates[i].IsButtonDown(button) &&
LastGamePadStates[i].IsButtonUp(button));
}
else
{
// Accept input from any player.
return (IsNewButtonPress(button, PlayerIndex.One, out playerIndex) ||
IsNewButtonPress(button, PlayerIndex.Two, out playerIndex) ||
IsNewButtonPress(button, PlayerIndex.Three, out playerIndex) ||
IsNewButtonPress(button, PlayerIndex.Four, out playerIndex));
}
}
///
/// Checks for a "menu select" input action.
/// The controllingPlayer parameter specifies which player to read input for.
/// If this is null, it will accept input from any player. When the action
/// is detected, the output playerIndex reports which player pressed it.
///
public bool IsMenuSelect(PlayerIndex? controllingPlayer,
out PlayerIndex playerIndex)
{
return IsNewKeyPress(Keys.Space, controllingPlayer, out playerIndex) ||
IsNewKeyPress(Keys.Enter, controllingPlayer, out playerIndex) ||
IsNewButtonPress(Buttons.A, controllingPlayer, out playerIndex) ||
IsNewButtonPress(Buttons.Start, controllingPlayer, out playerIndex);
}
///
/// Checks for a "menu cancel" input action.
/// The controllingPlayer parameter specifies which player to read input for.
/// If this is null, it will accept input from any player. When the action
/// is detected, the output playerIndex reports which player pressed it.
///
public bool IsMenuCancel(PlayerIndex? controllingPlayer,
out PlayerIndex playerIndex)
{
return IsNewKeyPress(Keys.Escape, controllingPlayer, out playerIndex) ||
IsNewButtonPress(Buttons.B, controllingPlayer, out playerIndex) ||
IsNewButtonPress(Buttons.Back, controllingPlayer, out playerIndex);
}
///
/// Checks for a "menu up" input action.
/// The controllingPlayer parameter specifies which player to read
/// input for. If this is null, it will accept input from any player.
///
public bool IsMenuUp(PlayerIndex? controllingPlayer)
{
PlayerIndex playerIndex;
return IsNewKeyPress(Keys.Up, controllingPlayer, out playerIndex) ||
IsNewKeyPress(Keys.Left, controllingPlayer, out playerIndex) ||
IsNewButtonPress(Buttons.DPadLeft, controllingPlayer, out playerIndex) ||
IsNewButtonPress(Buttons.LeftThumbstickLeft, controllingPlayer, out playerIndex);
}
///
/// Checks for a "menu down" input action.
/// The controllingPlayer parameter specifies which player to read
/// input for. If this is null, it will accept input from any player.
///
public bool IsMenuDown(PlayerIndex? controllingPlayer)
{
PlayerIndex playerIndex;
return IsNewKeyPress(Keys.Down, controllingPlayer, out playerIndex) ||
IsNewKeyPress(Keys.Right, controllingPlayer, out playerIndex) ||
IsNewButtonPress(Buttons.DPadRight, controllingPlayer, out playerIndex) ||
IsNewButtonPress(Buttons.LeftThumbstickRight, controllingPlayer, out playerIndex);
}
///
/// Checks for a "pause the game" input action.
/// The controllingPlayer parameter specifies which player to read
/// input for. If this is null, it will accept input from any player.
///
public bool IsPauseGame(PlayerIndex? controllingPlayer)
{
PlayerIndex playerIndex;
return IsNewKeyPress(Keys.Escape, controllingPlayer, out playerIndex) ||
IsNewButtonPress(Buttons.Back, controllingPlayer, out playerIndex) ||
IsNewButtonPress(Buttons.Start, controllingPlayer, out playerIndex);
}
///
/// Updates the matrix used to transform input coordinates.
///
/// The transformation matrix to apply.
public void UpdateInputTransformation(Matrix inputTransformation)
{
this.inputTransformation = inputTransformation;
}
///
/// Transforms touch/mouse positions from screen space to game space.
///
/// The screen-space position to transform.
/// The transformed position in game space.
public Vector2 TransformCursorLocation(Vector2 mousePosition)
{
// Transform back to cursor location
return Vector2.Transform(mousePosition, inputTransformation);
}
}
}