InputState.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  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 mouse cursor location every frame (like touch does)
  114. if (UIUtility.IsDesktop)
  115. {
  116. // Always track the current mouse cursor position
  117. // Note: lastCursorLocation is NOT updated here - it's only updated on actual clicks/taps
  118. currentCursorLocation = TransformCursorLocation(new Vector2(CurrentMouseState.X, CurrentMouseState.Y));
  119. }
  120. // Update touch state
  121. touchCount = 0;
  122. LastTouchState = CurrentTouchState;
  123. CurrentTouchState = TouchPanel.GetState();
  124. // Process all available gestures
  125. Gestures.Clear();
  126. while (TouchPanel.IsGestureAvailable)
  127. {
  128. GestureSample gesture = TouchPanel.ReadGesture();
  129. Gestures.Add(gesture);
  130. // Update cursor location from gesture position (for tap and other gestures)
  131. if (gesture.GestureType == GestureType.Tap)
  132. {
  133. lastCursorLocation = currentCursorLocation;
  134. // Transform gesture position to game coordinates
  135. currentCursorLocation = TransformCursorLocation(gesture.Position);
  136. }
  137. }
  138. // Process touch inputs
  139. foreach (TouchLocation location in CurrentTouchState)
  140. {
  141. switch (location.State)
  142. {
  143. case TouchLocationState.Pressed:
  144. touchCount++;
  145. lastCursorLocation = currentCursorLocation;
  146. // Transform touch position to game coordinates
  147. currentCursorLocation = TransformCursorLocation(location.Position);
  148. break;
  149. case TouchLocationState.Moved:
  150. break;
  151. case TouchLocationState.Released:
  152. break;
  153. }
  154. }
  155. // Handle mouse clicks as touch equivalents
  156. if (IsLeftMouseButtonClicked())
  157. {
  158. lastCursorLocation = currentCursorLocation;
  159. touchCount = 1;
  160. }
  161. if (IsMiddleMouseButtonClicked())
  162. {
  163. lastCursorLocation = currentCursorLocation;
  164. touchCount = 2; // Treat middle mouse click as double touch
  165. }
  166. if (IsRightMouseButtonClicked())
  167. {
  168. lastCursorLocation = currentCursorLocation;
  169. touchCount = 3; // Treat right mouse click as triple touch
  170. }
  171. // Reset mouse wheel flags
  172. isMouseWheelScrolledUp = false;
  173. isMouseWheelScrolledDown = false;
  174. // Detect mouse wheel scrolling
  175. if (CurrentMouseState.ScrollWheelValue != LastMouseState.ScrollWheelValue)
  176. {
  177. int scrollWheelDelta = CurrentMouseState.ScrollWheelValue - LastMouseState.ScrollWheelValue;
  178. // Handle the scroll wheel event based on the delta
  179. if (scrollWheelDelta > 0)
  180. {
  181. // Mouse wheel scrolled up
  182. isMouseWheelScrolledUp = true;
  183. }
  184. else if (scrollWheelDelta < 0)
  185. {
  186. // Mouse wheel scrolled down
  187. isMouseWheelScrolledDown = true;
  188. }
  189. }
  190. // Update the cursor location using gamepad and keyboard
  191. float elapsedTime = (float)gameTime.ElapsedGameTime.TotalSeconds;
  192. // Move cursor with 1st gamepad thumbstick
  193. if (CurrentGamePadStates[0].IsConnected)
  194. {
  195. lastCursorLocation = currentCursorLocation;
  196. currentCursorLocation.X += CurrentGamePadStates[0].ThumbSticks.Left.X * elapsedTime * cursorMoveSpeed;
  197. currentCursorLocation.Y -= CurrentGamePadStates[0].ThumbSticks.Left.Y * elapsedTime * cursorMoveSpeed;
  198. }
  199. // Keep cursor within bounds
  200. currentCursorLocation.X = MathHelper.Clamp(currentCursorLocation.X, 0f, baseBufferWidth);
  201. currentCursorLocation.Y = MathHelper.Clamp(currentCursorLocation.Y, 0f, baseBufferHeight);
  202. }
  203. /// <summary>
  204. /// Checks if left mouse button was clicked (pressed and then released)
  205. /// </summary>
  206. /// <returns>True if left mouse button was clicked, false otherwise.</returns>
  207. public bool IsLeftMouseButtonClicked()
  208. {
  209. return CurrentMouseState.LeftButton == ButtonState.Released && LastMouseState.LeftButton == ButtonState.Pressed;
  210. }
  211. /// <summary>
  212. /// Checks if middle mouse button was clicked (pressed and then released)
  213. /// </summary>
  214. /// <returns>True if middle mouse button was clicked, false otherwise.</returns>
  215. internal bool IsMiddleMouseButtonClicked()
  216. {
  217. return CurrentMouseState.MiddleButton == ButtonState.Released && LastMouseState.MiddleButton == ButtonState.Pressed;
  218. }
  219. /// <summary>
  220. /// Checks if right mouse button was clicked (pressed and then released)
  221. /// </summary>
  222. /// <returns>True if right mouse button was clicked, false otherwise.</returns>
  223. internal bool IsRightMouseButtonClicked()
  224. {
  225. return CurrentMouseState.RightButton == ButtonState.Released && LastMouseState.RightButton == ButtonState.Pressed;
  226. }
  227. /// <summary>
  228. /// Helper for checking if a key was newly pressed during this update. The
  229. /// controllingPlayer parameter specifies which player to read input for.
  230. /// If this is null, it will accept input from any player. When a keypress
  231. /// is detected, the output playerIndex reports which player pressed it.
  232. /// </summary>
  233. public bool IsNewKeyPress(Keys key, PlayerIndex? controllingPlayer,
  234. out PlayerIndex playerIndex)
  235. {
  236. if (controllingPlayer.HasValue)
  237. {
  238. // Read input from the specified player.
  239. playerIndex = controllingPlayer.Value;
  240. int i = (int)playerIndex;
  241. return (CurrentKeyboardStates[i].IsKeyDown(key) &&
  242. LastKeyboardStates[i].IsKeyUp(key));
  243. }
  244. else
  245. {
  246. // Accept input from any player.
  247. return (IsNewKeyPress(key, PlayerIndex.One, out playerIndex) ||
  248. IsNewKeyPress(key, PlayerIndex.Two, out playerIndex) ||
  249. IsNewKeyPress(key, PlayerIndex.Three, out playerIndex) ||
  250. IsNewKeyPress(key, PlayerIndex.Four, out playerIndex));
  251. }
  252. }
  253. /// <summary>
  254. /// Helper for checking if a button was newly pressed during this update.
  255. /// The controllingPlayer parameter specifies which player to read input for.
  256. /// If this is null, it will accept input from any player. When a button press
  257. /// is detected, the output playerIndex reports which player pressed it.
  258. /// </summary>
  259. public bool IsNewButtonPress(Buttons button, PlayerIndex? controllingPlayer,
  260. out PlayerIndex playerIndex)
  261. {
  262. if (controllingPlayer.HasValue)
  263. {
  264. // Read input from the specified player.
  265. playerIndex = controllingPlayer.Value;
  266. int i = (int)playerIndex;
  267. return (CurrentGamePadStates[i].IsButtonDown(button) &&
  268. LastGamePadStates[i].IsButtonUp(button));
  269. }
  270. else
  271. {
  272. // Accept input from any player.
  273. return (IsNewButtonPress(button, PlayerIndex.One, out playerIndex) ||
  274. IsNewButtonPress(button, PlayerIndex.Two, out playerIndex) ||
  275. IsNewButtonPress(button, PlayerIndex.Three, out playerIndex) ||
  276. IsNewButtonPress(button, PlayerIndex.Four, out playerIndex));
  277. }
  278. }
  279. /// <summary>
  280. /// Checks for a "menu select" input action.
  281. /// The controllingPlayer parameter specifies which player to read input for.
  282. /// If this is null, it will accept input from any player. When the action
  283. /// is detected, the output playerIndex reports which player pressed it.
  284. /// </summary>
  285. public bool IsMenuSelect(PlayerIndex? controllingPlayer,
  286. out PlayerIndex playerIndex)
  287. {
  288. return IsNewKeyPress(Keys.Space, controllingPlayer, out playerIndex) ||
  289. IsNewKeyPress(Keys.Enter, controllingPlayer, out playerIndex) ||
  290. IsNewButtonPress(Buttons.A, controllingPlayer, out playerIndex) ||
  291. IsNewButtonPress(Buttons.Start, controllingPlayer, out playerIndex);
  292. }
  293. /// <summary>
  294. /// Checks for a "menu cancel" input action.
  295. /// The controllingPlayer parameter specifies which player to read input for.
  296. /// If this is null, it will accept input from any player. When the action
  297. /// is detected, the output playerIndex reports which player pressed it.
  298. /// </summary>
  299. public bool IsMenuCancel(PlayerIndex? controllingPlayer,
  300. out PlayerIndex playerIndex)
  301. {
  302. return IsNewKeyPress(Keys.Escape, controllingPlayer, out playerIndex) ||
  303. IsNewButtonPress(Buttons.B, controllingPlayer, out playerIndex) ||
  304. IsNewButtonPress(Buttons.Back, controllingPlayer, out playerIndex);
  305. }
  306. /// <summary>
  307. /// Checks for a "menu up" input action.
  308. /// The controllingPlayer parameter specifies which player to read
  309. /// input for. If this is null, it will accept input from any player.
  310. /// </summary>
  311. public bool IsMenuUp(PlayerIndex? controllingPlayer)
  312. {
  313. PlayerIndex playerIndex;
  314. return IsNewKeyPress(Keys.Up, controllingPlayer, out playerIndex) ||
  315. IsNewKeyPress(Keys.Left, controllingPlayer, out playerIndex) ||
  316. IsNewButtonPress(Buttons.DPadLeft, controllingPlayer, out playerIndex) ||
  317. IsNewButtonPress(Buttons.LeftThumbstickLeft, controllingPlayer, out playerIndex);
  318. }
  319. /// <summary>
  320. /// Checks for a "menu down" input action.
  321. /// The controllingPlayer parameter specifies which player to read
  322. /// input for. If this is null, it will accept input from any player.
  323. /// </summary>
  324. public bool IsMenuDown(PlayerIndex? controllingPlayer)
  325. {
  326. PlayerIndex playerIndex;
  327. return IsNewKeyPress(Keys.Down, controllingPlayer, out playerIndex) ||
  328. IsNewKeyPress(Keys.Right, controllingPlayer, out playerIndex) ||
  329. IsNewButtonPress(Buttons.DPadRight, controllingPlayer, out playerIndex) ||
  330. IsNewButtonPress(Buttons.LeftThumbstickRight, controllingPlayer, out playerIndex);
  331. }
  332. /// <summary>
  333. /// Checks for a "pause the game" input action.
  334. /// The controllingPlayer parameter specifies which player to read
  335. /// input for. If this is null, it will accept input from any player.
  336. /// </summary>
  337. public bool IsPauseGame(PlayerIndex? controllingPlayer)
  338. {
  339. PlayerIndex playerIndex;
  340. return IsNewKeyPress(Keys.Escape, controllingPlayer, out playerIndex) ||
  341. IsNewButtonPress(Buttons.Back, controllingPlayer, out playerIndex) ||
  342. IsNewButtonPress(Buttons.Start, controllingPlayer, out playerIndex);
  343. }
  344. /// <summary>
  345. /// Updates the matrix used to transform input coordinates.
  346. /// </summary>
  347. /// <param name="inputTransformation">The transformation matrix to apply.</param>
  348. public void UpdateInputTransformation(Matrix inputTransformation)
  349. {
  350. this.inputTransformation = inputTransformation;
  351. }
  352. /// <summary>
  353. /// Transforms touch/mouse positions from screen space to game space.
  354. /// </summary>
  355. /// <param name="mousePosition">The screen-space position to transform.</param>
  356. /// <returns>The transformed position in game space.</returns>
  357. public Vector2 TransformCursorLocation(Vector2 mousePosition)
  358. {
  359. // Transform back to cursor location
  360. return Vector2.Transform(mousePosition, inputTransformation);
  361. }
  362. }
  363. }