InputState.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. //-----------------------------------------------------------------------------
  2. // InputState.cs
  3. //
  4. // Microsoft XNA Community Game Platform
  5. // Copyright (C) Microsoft Corporation. All rights reserved.
  6. //-----------------------------------------------------------------------------
  7. using Microsoft.Xna.Framework;
  8. using Microsoft.Xna.Framework.Input;
  9. using Microsoft.Xna.Framework.Input.Touch;
  10. using System;
  11. using System.Collections.Generic;
  12. namespace CardsFramework
  13. {
  14. /// <summary>
  15. /// Helper for reading input from keyboard, gamepad, and touch input. This class
  16. /// tracks both the current and previous state of the input devices, and implements
  17. /// query methods for high level input actions such as "move up through the menu"
  18. /// or "pause the game".
  19. /// </summary>
  20. public class InputState
  21. {
  22. public const int MaxInputs = 4; // Maximum number of supported input devices (e.g., players)
  23. // Current Inputstates - Tracks the latest state of all input devices
  24. public readonly GamePadState[] CurrentGamePadStates;
  25. public readonly KeyboardState[] CurrentKeyboardStates;
  26. public MouseState CurrentMouseState;
  27. private int touchCount; // Number of active touch inputs
  28. public TouchCollection CurrentTouchState;
  29. // Last Inputstates - Stores the previous frame's input states for detecting changes
  30. public readonly GamePadState[] LastGamePadStates;
  31. public readonly KeyboardState[] LastKeyboardStates;
  32. public MouseState LastMouseState;
  33. public TouchCollection LastTouchState;
  34. public readonly List<GestureSample> Gestures = new List<GestureSample>(); // Stores touch gestures
  35. public readonly bool[] GamePadWasConnected;
  36. /// <summary>
  37. /// Cursor move speed in pixels per second
  38. /// </summary>
  39. private const float cursorMoveSpeed = 250.0f;
  40. private Vector2 currentCursorLocation;
  41. /// <summary>
  42. /// Current location of our Cursor
  43. /// </summary>
  44. public Vector2 CurrentCursorLocation => currentCursorLocation;
  45. private Vector2 lastCursorLocation;
  46. /// <summary>
  47. /// Current location of our Cursor
  48. /// </summary>
  49. public Vector2 LastCursorLocation => lastCursorLocation;
  50. private bool isMouseWheelScrolledDown;
  51. /// <summary>
  52. /// Has the user scrolled the mouse wheel down?
  53. /// </summary>
  54. public bool IsMouseWheelScrolledDown => isMouseWheelScrolledDown;
  55. private bool isMouseWheelScrolledUp;
  56. private Matrix inputTransformation; // Used to transform input coordinates between screen and game space
  57. private float baseBufferWidth;
  58. private float baseBufferHeight;
  59. /// <summary>
  60. /// Has the user scrolled the mouse wheel up?
  61. /// </summary>
  62. public bool IsMouseWheelScrolledUp => isMouseWheelScrolledUp;
  63. /// <summary>
  64. /// Constructs a new input state.
  65. /// </summary>
  66. public InputState(float baseBufferWidth, float baseBufferHeight)
  67. {
  68. this.baseBufferWidth = baseBufferWidth;
  69. this.baseBufferHeight = baseBufferHeight;
  70. // Initialize arrays for multiple controller/keyboard states
  71. CurrentKeyboardStates = new KeyboardState[MaxInputs];
  72. CurrentGamePadStates = new GamePadState[MaxInputs];
  73. LastKeyboardStates = new KeyboardState[MaxInputs];
  74. LastGamePadStates = new GamePadState[MaxInputs];
  75. GamePadWasConnected = new bool[MaxInputs];
  76. // Configure platform-specific input options
  77. if (UIUtility.IsMobile)
  78. {
  79. TouchPanel.EnabledGestures = GestureType.Tap;
  80. }
  81. else if (UIUtility.IsDesktop)
  82. {
  83. // No desktop-specific initialization needed
  84. }
  85. else
  86. {
  87. // For now, we'll throw an exception if we don't know the platform
  88. throw new PlatformNotSupportedException();
  89. }
  90. }
  91. /// <summary>
  92. /// Reads the latest state of the keyboard and gamepad.
  93. /// </summary>
  94. public void Update(GameTime gameTime)
  95. {
  96. // Update keyboard and gamepad states for all players
  97. for (int i = 0; i < MaxInputs; i++)
  98. {
  99. LastKeyboardStates[i] = CurrentKeyboardStates[i];
  100. LastGamePadStates[i] = CurrentGamePadStates[i];
  101. CurrentKeyboardStates[i] = Keyboard.GetState();
  102. CurrentGamePadStates[i] = GamePad.GetState((PlayerIndex)i);
  103. // Keep track of whether a gamepad has ever been
  104. // connected, so we can detect if it is unplugged.
  105. if (CurrentGamePadStates[i].IsConnected)
  106. {
  107. GamePadWasConnected[i] = true;
  108. }
  109. }
  110. // Update mouse state
  111. LastMouseState = CurrentMouseState;
  112. CurrentMouseState = Mouse.GetState();
  113. // Update touch state
  114. touchCount = 0;
  115. LastTouchState = CurrentTouchState;
  116. CurrentTouchState = TouchPanel.GetState();
  117. // Process all available gestures
  118. Gestures.Clear();
  119. while (TouchPanel.IsGestureAvailable)
  120. {
  121. GestureSample gesture = TouchPanel.ReadGesture();
  122. Gestures.Add(gesture);
  123. // Update cursor location from gesture position (for tap and other gestures)
  124. if (gesture.GestureType == GestureType.Tap)
  125. {
  126. lastCursorLocation = currentCursorLocation;
  127. // Transform gesture position to game coordinates
  128. currentCursorLocation = TransformCursorLocation(gesture.Position);
  129. }
  130. }
  131. // Process touch inputs
  132. foreach (TouchLocation location in CurrentTouchState)
  133. {
  134. switch (location.State)
  135. {
  136. case TouchLocationState.Pressed:
  137. touchCount++;
  138. lastCursorLocation = currentCursorLocation;
  139. // Transform touch position to game coordinates
  140. currentCursorLocation = TransformCursorLocation(location.Position);
  141. break;
  142. case TouchLocationState.Moved:
  143. break;
  144. case TouchLocationState.Released:
  145. break;
  146. }
  147. }
  148. // Handle mouse clicks as touch equivalents
  149. if (IsLeftMouseButtonClicked())
  150. {
  151. lastCursorLocation = currentCursorLocation;
  152. // Transform mouse position to game coordinates
  153. currentCursorLocation = TransformCursorLocation(new Vector2(CurrentMouseState.X, CurrentMouseState.Y));
  154. touchCount = 1;
  155. }
  156. if (IsMiddleMouseButtonClicked())
  157. {
  158. touchCount = 2; // Treat middle mouse click as double touch
  159. }
  160. if (IsRightMoustButtonClicked())
  161. {
  162. touchCount = 3; // Treat right mouse click as triple touch
  163. }
  164. // Reset mouse wheel flags
  165. isMouseWheelScrolledUp = false;
  166. isMouseWheelScrolledDown = false;
  167. // Detect mouse wheel scrolling
  168. if (CurrentMouseState.ScrollWheelValue != LastMouseState.ScrollWheelValue)
  169. {
  170. int scrollWheelDelta = CurrentMouseState.ScrollWheelValue - LastMouseState.ScrollWheelValue;
  171. // Handle the scroll wheel event based on the delta
  172. if (scrollWheelDelta > 0)
  173. {
  174. // Mouse wheel scrolled up
  175. isMouseWheelScrolledUp = true;
  176. }
  177. else if (scrollWheelDelta < 0)
  178. {
  179. // Mouse wheel scrolled down
  180. isMouseWheelScrolledDown = true;
  181. }
  182. }
  183. // Update the cursor location using gamepad and keyboard
  184. float elapsedTime = (float)gameTime.ElapsedGameTime.TotalSeconds;
  185. // Move cursor with 1st gamepad thumbstick
  186. if (CurrentGamePadStates[0].IsConnected)
  187. {
  188. lastCursorLocation = currentCursorLocation;
  189. currentCursorLocation.X += CurrentGamePadStates[0].ThumbSticks.Left.X * elapsedTime * cursorMoveSpeed;
  190. currentCursorLocation.Y -= CurrentGamePadStates[0].ThumbSticks.Left.Y * elapsedTime * cursorMoveSpeed;
  191. }
  192. // Keep cursor within bounds
  193. currentCursorLocation.X = MathHelper.Clamp(currentCursorLocation.X, 0f, baseBufferWidth);
  194. currentCursorLocation.Y = MathHelper.Clamp(currentCursorLocation.Y, 0f, baseBufferHeight);
  195. }
  196. /// <summary>
  197. /// Checks if left mouse button was clicked (pressed and then released)
  198. /// </summary>
  199. /// <returns>True if left mouse button was clicked, false otherwise.</returns>
  200. internal bool IsLeftMouseButtonClicked()
  201. {
  202. return CurrentMouseState.LeftButton == ButtonState.Released && LastMouseState.LeftButton == ButtonState.Pressed;
  203. }
  204. /// <summary>
  205. /// Checks if middle mouse button was clicked (pressed and then released)
  206. /// </summary>
  207. /// <returns>True if middle mouse button was clicked, false otherwise.</returns>
  208. internal bool IsMiddleMouseButtonClicked()
  209. {
  210. return CurrentMouseState.MiddleButton == ButtonState.Released && LastMouseState.MiddleButton == ButtonState.Pressed;
  211. }
  212. /// <summary>
  213. /// Checks if right mouse button was clicked (pressed and then released)
  214. /// </summary>
  215. /// <returns>True if right mouse button was clicked, false otherwise.</returns>
  216. internal bool IsRightMoustButtonClicked()
  217. {
  218. return CurrentMouseState.RightButton == ButtonState.Released && LastMouseState.RightButton == ButtonState.Pressed;
  219. }
  220. /// <summary>
  221. /// Helper for checking if a key was newly pressed during this update. The
  222. /// controllingPlayer parameter specifies which player to read input for.
  223. /// If this is null, it will accept input from any player. When a keypress
  224. /// is detected, the output playerIndex reports which player pressed it.
  225. /// </summary>
  226. public bool IsNewKeyPress(Keys key, PlayerIndex? controllingPlayer,
  227. out PlayerIndex playerIndex)
  228. {
  229. if (controllingPlayer.HasValue)
  230. {
  231. // Read input from the specified player.
  232. playerIndex = controllingPlayer.Value;
  233. int i = (int)playerIndex;
  234. return (CurrentKeyboardStates[i].IsKeyDown(key) &&
  235. LastKeyboardStates[i].IsKeyUp(key));
  236. }
  237. else
  238. {
  239. // Accept input from any player.
  240. return (IsNewKeyPress(key, PlayerIndex.One, out playerIndex) ||
  241. IsNewKeyPress(key, PlayerIndex.Two, out playerIndex) ||
  242. IsNewKeyPress(key, PlayerIndex.Three, out playerIndex) ||
  243. IsNewKeyPress(key, PlayerIndex.Four, out playerIndex));
  244. }
  245. }
  246. /// <summary>
  247. /// Helper for checking if a button was newly pressed during this update.
  248. /// The controllingPlayer parameter specifies which player to read input for.
  249. /// If this is null, it will accept input from any player. When a button press
  250. /// is detected, the output playerIndex reports which player pressed it.
  251. /// </summary>
  252. public bool IsNewButtonPress(Buttons button, PlayerIndex? controllingPlayer,
  253. out PlayerIndex playerIndex)
  254. {
  255. if (controllingPlayer.HasValue)
  256. {
  257. // Read input from the specified player.
  258. playerIndex = controllingPlayer.Value;
  259. int i = (int)playerIndex;
  260. return (CurrentGamePadStates[i].IsButtonDown(button) &&
  261. LastGamePadStates[i].IsButtonUp(button));
  262. }
  263. else
  264. {
  265. // Accept input from any player.
  266. return (IsNewButtonPress(button, PlayerIndex.One, out playerIndex) ||
  267. IsNewButtonPress(button, PlayerIndex.Two, out playerIndex) ||
  268. IsNewButtonPress(button, PlayerIndex.Three, out playerIndex) ||
  269. IsNewButtonPress(button, PlayerIndex.Four, out playerIndex));
  270. }
  271. }
  272. /// <summary>
  273. /// Checks for a "menu select" input action.
  274. /// The controllingPlayer parameter specifies which player to read input for.
  275. /// If this is null, it will accept input from any player. When the action
  276. /// is detected, the output playerIndex reports which player pressed it.
  277. /// </summary>
  278. public bool IsMenuSelect(PlayerIndex? controllingPlayer,
  279. out PlayerIndex playerIndex)
  280. {
  281. return IsNewKeyPress(Keys.Space, controllingPlayer, out playerIndex) ||
  282. IsNewKeyPress(Keys.Enter, controllingPlayer, out playerIndex) ||
  283. IsNewButtonPress(Buttons.A, controllingPlayer, out playerIndex) ||
  284. IsNewButtonPress(Buttons.Start, controllingPlayer, out playerIndex);
  285. }
  286. /// <summary>
  287. /// Checks for a "menu cancel" input action.
  288. /// The controllingPlayer parameter specifies which player to read input for.
  289. /// If this is null, it will accept input from any player. When the action
  290. /// is detected, the output playerIndex reports which player pressed it.
  291. /// </summary>
  292. public bool IsMenuCancel(PlayerIndex? controllingPlayer,
  293. out PlayerIndex playerIndex)
  294. {
  295. return IsNewKeyPress(Keys.Escape, controllingPlayer, out playerIndex) ||
  296. IsNewButtonPress(Buttons.B, controllingPlayer, out playerIndex) ||
  297. IsNewButtonPress(Buttons.Back, controllingPlayer, out playerIndex);
  298. }
  299. /// <summary>
  300. /// Checks for a "menu up" input action.
  301. /// The controllingPlayer parameter specifies which player to read
  302. /// input for. If this is null, it will accept input from any player.
  303. /// </summary>
  304. public bool IsMenuUp(PlayerIndex? controllingPlayer)
  305. {
  306. PlayerIndex playerIndex;
  307. return IsNewKeyPress(Keys.Up, controllingPlayer, out playerIndex) ||
  308. IsNewKeyPress(Keys.Left, controllingPlayer, out playerIndex) ||
  309. IsNewButtonPress(Buttons.DPadLeft, controllingPlayer, out playerIndex) ||
  310. IsNewButtonPress(Buttons.LeftThumbstickLeft, controllingPlayer, out playerIndex);
  311. }
  312. /// <summary>
  313. /// Checks for a "menu down" input action.
  314. /// The controllingPlayer parameter specifies which player to read
  315. /// input for. If this is null, it will accept input from any player.
  316. /// </summary>
  317. public bool IsMenuDown(PlayerIndex? controllingPlayer)
  318. {
  319. PlayerIndex playerIndex;
  320. return IsNewKeyPress(Keys.Down, controllingPlayer, out playerIndex) ||
  321. IsNewKeyPress(Keys.Right, controllingPlayer, out playerIndex) ||
  322. IsNewButtonPress(Buttons.DPadRight, controllingPlayer, out playerIndex) ||
  323. IsNewButtonPress(Buttons.LeftThumbstickRight, controllingPlayer, out playerIndex);
  324. }
  325. /// <summary>
  326. /// Checks for a "pause the game" input action.
  327. /// The controllingPlayer parameter specifies which player to read
  328. /// input for. If this is null, it will accept input from any player.
  329. /// </summary>
  330. public bool IsPauseGame(PlayerIndex? controllingPlayer)
  331. {
  332. PlayerIndex playerIndex;
  333. return IsNewKeyPress(Keys.Escape, controllingPlayer, out playerIndex) ||
  334. IsNewButtonPress(Buttons.Back, controllingPlayer, out playerIndex) ||
  335. IsNewButtonPress(Buttons.Start, controllingPlayer, out playerIndex);
  336. }
  337. /// <summary>
  338. /// Updates the matrix used to transform input coordinates.
  339. /// </summary>
  340. /// <param name="inputTransformation">The transformation matrix to apply.</param>
  341. public void UpdateInputTransformation(Matrix inputTransformation)
  342. {
  343. this.inputTransformation = inputTransformation;
  344. }
  345. /// <summary>
  346. /// Transforms touch/mouse positions from screen space to game space.
  347. /// </summary>
  348. /// <param name="mousePosition">The screen-space position to transform.</param>
  349. /// <returns>The transformed position in game space.</returns>
  350. public Vector2 TransformCursorLocation(Vector2 mousePosition)
  351. {
  352. // Transform back to cursor location
  353. return Vector2.Transform(mousePosition, inputTransformation);
  354. }
  355. }
  356. }