PageRenderTime 45ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/InputSystem/Windows/WindowsMouse.cs

#
C# | 550 lines | 312 code | 42 blank | 196 comment | 60 complexity | f37161b886e7d51e91094b8cc8a11607 MD5 | raw file
Possible License(s): Apache-2.0
  1. using System;
  2. using System.Windows.Forms;
  3. using Delta.Engine;
  4. using Delta.InputSystem.Devices;
  5. using Delta.Platforms.Windows;
  6. using Delta.Utilities.Datatypes;
  7. using DeltaApp = Delta.Engine.Application;
  8. using DeltaScreen = Delta.Engine.ScreenSpace;
  9. using WPFMouseWheelEventArgs = System.Windows.Input.MouseWheelEventArgs;
  10. namespace Delta.InputSystem.Windows
  11. {
  12. /// <summary>
  13. /// Windows mouse implementation, which is based on windows forms events
  14. /// unlike XnaMouse, which is based on XNA Mouse.GetState polling. The
  15. /// advantage of using events is that we will able to detect more clicks
  16. /// in low frame situations. The disadvantage is that this will only work
  17. /// on Windows (and platforms capable of System.Windows.Forms like Linux
  18. /// and Mac via the Mono framework). The Windows input device classes should
  19. /// always be used for editors because they only work on Windows anyways
  20. /// and using events is great for editors (good for low frame rates and
  21. /// more accurate, it is even give us better performance than XnaMouse, but
  22. /// most importantly the WindowsKeyboard implementation is so much better
  23. /// for Editors because it catches all keyboard key presses, while
  24. /// XnaKeyboard misses some from time to time due to polling).
  25. /// </summary>
  26. public class WindowsMouse : BaseMouse
  27. {
  28. #region IsConnected (Public)
  29. /// <summary>
  30. /// Is connected, will always return true because this assembly will only
  31. /// be able to load on Windows platforms anyway.
  32. /// </summary>
  33. public override bool IsConnected
  34. {
  35. get
  36. {
  37. // We only support one mouse via normal Windows events (use MultiMouse
  38. // for multiple mouses)!
  39. return Index == 0;
  40. }
  41. }
  42. #endregion
  43. #region Private
  44. #region lastScrollWheelValue (Private)
  45. /// <summary>
  46. /// Because we update the TotalScrollWheel value directly based on the
  47. /// events we receive, calculating the ScrollWheelDelta works a little
  48. /// different here. We need to check and update it in the Update method.
  49. /// </summary>
  50. private float lastScrollWheelValue;
  51. #endregion
  52. #region newEventMouseLeftDown (Private)
  53. /// <summary>
  54. /// Remember the events that happened in order to correctly update each
  55. /// mouse button in the Update method.
  56. /// </summary>
  57. private bool newEventMouseLeftDown;
  58. #endregion
  59. #region newEventMouseRightDown (Private)
  60. /// <summary>
  61. /// Remember the events that happened in order to correctly update each
  62. /// mouse button in the Update method.
  63. /// </summary>
  64. private bool newEventMouseRightDown;
  65. #endregion
  66. #region newEventMouseMiddleDown (Private)
  67. /// <summary>
  68. /// Remember the events that happened in order to correctly update each
  69. /// mouse button in the Update method.
  70. /// </summary>
  71. private bool newEventMouseMiddleDown;
  72. #endregion
  73. #region newEventMouseExtraOneDown (Private)
  74. /// <summary>
  75. /// Remember the events that happened in order to correctly update each
  76. /// mouse button in the Update method.
  77. /// </summary>
  78. private bool newEventMouseExtraOneDown;
  79. #endregion
  80. #region newEventMouseExtraTwoDown (Private)
  81. /// <summary>
  82. /// Remember the events that happened in order to correctly update each
  83. /// mouse button in the Update method.
  84. /// </summary>
  85. private bool newEventMouseExtraTwoDown;
  86. #endregion
  87. #region newEventMouseLeftUp (Private)
  88. /// <summary>
  89. /// Remember the events that happened in order to correctly update each
  90. /// mouse button in the Update method.
  91. /// </summary>
  92. private bool newEventMouseLeftUp;
  93. #endregion
  94. #region newEventMouseRightUp (Private)
  95. /// <summary>
  96. /// Remember the events that happened in order to correctly update each
  97. /// mouse button in the Update method.
  98. /// </summary>
  99. private bool newEventMouseRightUp;
  100. #endregion
  101. #region newEventMouseMiddleUp (Private)
  102. /// <summary>
  103. /// Remember the events that happened in order to correctly update each
  104. /// mouse button in the Update method.
  105. /// </summary>
  106. private bool newEventMouseMiddleUp;
  107. #endregion
  108. #region newEventMouseExtraOneUp (Private)
  109. /// <summary>
  110. /// Remember the events that happened in order to correctly update each
  111. /// mouse button in the Update method.
  112. /// </summary>
  113. private bool newEventMouseExtraOneUp;
  114. #endregion
  115. #region newEventMouseExtraTwoUp (Private)
  116. /// <summary>
  117. /// Remember the events that happened in order to correctly update each
  118. /// mouse button in the Update method.
  119. /// </summary>
  120. private bool newEventMouseExtraTwoUp;
  121. #endregion
  122. #region remClick (Private)
  123. /// <summary>
  124. /// Also remember clicks, double clicks and drag gestures as they happen
  125. /// in the events (more accurate than to check our simplified states,
  126. /// this is especially useful for low frame rate situations)!
  127. /// </summary>
  128. private bool remClick;
  129. #endregion
  130. #region remDoubleClick (Private)
  131. /// <summary>
  132. /// Also remember clicks, double clicks and drag gestures as they happen
  133. /// in the events (more accurate than to check our simplified states,
  134. /// this is especially useful for low frame rate situations)!
  135. /// </summary>
  136. private bool remDoubleClick;
  137. #endregion
  138. #region remDrag (Private)
  139. /// <summary>
  140. /// Also remember clicks, double clicks and drag gestures as they happen
  141. /// in the events (more accurate than to check our simplified states,
  142. /// this is especially useful for low frame rate situations)!
  143. /// </summary>
  144. private bool remDrag;
  145. #endregion
  146. #region thisFrameMousePosition (Private)
  147. /// <summary>
  148. /// Little helper variable to delay the current mouse position for exactly
  149. /// one frame so we have always a valid "lastFramePosition"
  150. /// </summary>
  151. private Point thisFrameMousePosition;
  152. #endregion
  153. #region thisFrameScrollWheelValue (Private)
  154. /// <summary>
  155. /// Little helper variable to delay the current mouse scroll value for
  156. /// exactly one frame so we have always a valid "lastFramePosition"
  157. /// </summary>
  158. private float thisFrameScrollWheelValue;
  159. #endregion
  160. #region thisFrameMouseMovement (Private)
  161. private Point thisFrameMouseMovement;
  162. #endregion
  163. #endregion
  164. #region Constructors
  165. /// <summary>
  166. /// Create WindowsMouse, will link up the events from the windows form
  167. /// to our event handling methods here.
  168. /// </summary>
  169. public WindowsMouse()
  170. {
  171. // We need to use the viewport handle for mouse input events
  172. Control viewport = Control.FromHandle(DeltaApp.Window.ViewportHandle);
  173. viewport.MouseDown += OnMouseDown;
  174. viewport.MouseUp += OnMouseUp;
  175. viewport.MouseMove += OnMouseMove;
  176. // Same as for XnaMouse we need to use the windows form for mouse scroll
  177. // events, they won't be forwarded to the viewport control. Also support
  178. // WPF (Windows Presentation Foundation) windows here (most editors).
  179. IntPtr nativeWindowHande = DeltaApp.Window.Handle;
  180. if (DeltaApp.Window.GetType() == typeof(WindowWPF))
  181. {
  182. viewport.MouseWheel += OnFormsWindowMouseWheel;
  183. }
  184. // Else this is a normal windows forms window (games and some editors)
  185. else
  186. {
  187. // For Windows Forms we have to ask the window itself, the control
  188. // will not get the mouse wheel events. Fix this if more advanced
  189. // editors should not capture all mouse wheel events!
  190. Control formsWindow = Control.FromHandle(nativeWindowHande);
  191. formsWindow.MouseWheel += OnFormsWindowMouseWheel;
  192. } // else
  193. }
  194. #endregion
  195. #region Methods (Private)
  196. #region OnMouseDown
  197. /// <summary>
  198. /// Handle on mouse down event.
  199. /// </summary>
  200. /// <param name="sender">Event sender</param>
  201. /// <param name="e">Event</param>
  202. private void OnMouseDown(object sender, MouseEventArgs e)
  203. {
  204. // Mark that we have data for the Update method.
  205. HasData = true;
  206. // And store some extra states for gestures detection.
  207. lastClickBeginTimeMs = clickBeginTimeMs;
  208. clickBeginTimeMs = Time.Milliseconds;
  209. // If the any mouse button was just beginning to be pressed, remember
  210. // the mouseStartClickPosition for DragStart (used for 5 gestures)!
  211. Point pixelPosition = new Point(e.X, e.Y);
  212. // For better double tap checks, also keep the last click position
  213. // around (it is not really a double tap if the mouse was moved a lot).
  214. clickStartPosition = DeltaScreen.ToQuadraticSpace(pixelPosition);
  215. // Remember the newly pressed button state
  216. if (e.Button == MouseButtons.Left)
  217. {
  218. // Is this a double click (can even happen in the same frame, which
  219. // makes sense if there was a long operation like loading something)?
  220. if (newEventMouseLeftDown ||
  221. buttons[(int)(InputButton.MouseLeft - StartIndexOfMouseButtons)] >=
  222. InputState.Pressed)
  223. {
  224. remDoubleClick = true;
  225. // Note: The MouseEventArgs Clicks property might also be useful, but
  226. // more for the MouseClick event, which we don't use here.
  227. } // if
  228. // Else just remember a normal click
  229. newEventMouseLeftDown = true;
  230. }
  231. else if (e.Button == MouseButtons.Right)
  232. {
  233. newEventMouseRightDown = true;
  234. }
  235. else if (e.Button == MouseButtons.Middle)
  236. {
  237. newEventMouseMiddleDown = true;
  238. }
  239. else if (e.Button == MouseButtons.XButton1)
  240. {
  241. newEventMouseExtraOneDown = true;
  242. }
  243. else if (e.Button == MouseButtons.XButton2)
  244. {
  245. newEventMouseExtraTwoDown = true;
  246. }
  247. }
  248. #endregion
  249. #region OnMouseUp
  250. /// <summary>
  251. /// Handle on mouse up event.
  252. /// </summary>
  253. /// <param name="sender">Event sender</param>
  254. /// <param name="e">Event</param>
  255. private void OnMouseUp(object sender, MouseEventArgs e)
  256. {
  257. // Mark that we have data for the Update method.
  258. HasData = true;
  259. // Remember the newly released button state
  260. if (e.Button == MouseButtons.Left)
  261. {
  262. newEventMouseLeftUp = true;
  263. // Was mouse also pressed this frame?
  264. if (newEventMouseLeftDown)
  265. {
  266. // Then this was a quick click, which we call MouseTap
  267. remClick = true;
  268. // And also check if we were dragging!
  269. remDrag = mousePosition != clickStartPosition;
  270. }
  271. }
  272. else if (e.Button == MouseButtons.Right)
  273. {
  274. newEventMouseRightUp = true;
  275. // Was mouse also pressed and dragged this frame?
  276. if (newEventMouseRightDown &&
  277. mousePosition != clickStartPosition)
  278. {
  279. remDrag = true;
  280. }
  281. }
  282. else if (e.Button == MouseButtons.Middle)
  283. {
  284. newEventMouseMiddleUp = true;
  285. // Was mouse also pressed and dragged this frame?
  286. if (newEventMouseMiddleDown &&
  287. mousePosition != clickStartPosition)
  288. {
  289. remDrag = true;
  290. }
  291. }
  292. else if (e.Button == MouseButtons.XButton1)
  293. {
  294. newEventMouseExtraOneUp = true;
  295. }
  296. else if (e.Button == MouseButtons.XButton2)
  297. {
  298. newEventMouseExtraTwoUp = true;
  299. }
  300. }
  301. #endregion
  302. #region OnMouseMove
  303. /// <summary>
  304. /// Handle on mouse move, will just update our mouse position.
  305. /// Note: If moving this event could occur more often than the frame rate,
  306. /// but since we only need to update the mousePosition when it changes
  307. /// this is still faster than doing it every frame.
  308. /// </summary>
  309. /// <param name="sender">Event sender</param>
  310. /// <param name="e">Event</param>
  311. private void OnMouseMove(object sender, MouseEventArgs e)
  312. {
  313. // Mark that we have data for the Update method.
  314. HasData = true;
  315. // and get the new mouse position.
  316. Point pixelPosition = new Point(e.X, e.Y);
  317. Point lastMousePos = mousePosition;
  318. mousePosition = DeltaScreen.ToQuadraticSpace(pixelPosition);
  319. thisFrameMouseMovement += mousePosition - lastMousePos;
  320. }
  321. #endregion
  322. #region OnFormsWindowMouseWheel
  323. /// <summary>
  324. /// On forms window mouse wheel
  325. /// </summary>
  326. /// <param name="sender">Event sender</param>
  327. /// <param name="e">Event</param>
  328. private void OnFormsWindowMouseWheel(object sender, MouseEventArgs e)
  329. {
  330. // Mark that we have data for the Update method.
  331. HasData = true;
  332. // Divide by 120.0 to make sure we have the same data as with XnaMouse
  333. TotalScrollWheel += e.Delta / 120.0f;
  334. }
  335. #endregion
  336. #region OnWPFWindowMouseWheel
  337. /// <summary>
  338. /// On WPF window mouse wheel
  339. /// </summary>
  340. /// <param name="sender">Event sender</param>
  341. /// <param name="e">Event</param>
  342. private void OnWPFWindowMouseWheel(object sender,
  343. WPFMouseWheelEventArgs e)
  344. {
  345. // Mark that we have data for the Update method.
  346. HasData = true;
  347. //// Save the last scroll wheel value.
  348. //lastScrollWheelValue = TotalScrollWheel;
  349. // Divide by 120.0 to make sure we have the same data as with XnaMouse
  350. TotalScrollWheel += e.Delta / 120.0f;
  351. }
  352. #endregion
  353. #region UpdateButton
  354. /// <summary>
  355. /// Update mouse button state based on the data remembered from events.
  356. /// </summary>
  357. /// <param name="wasMouseDown">Was mouse pressed</param>
  358. /// <param name="wasMouseUp">Was mouse released</param>
  359. /// <param name="mouseButton">Mouse button</param>
  360. private void UpdateButton(bool wasMouseDown, bool wasMouseUp,
  361. InputButton mouseButton)
  362. {
  363. // Only handle valid requests of actual Mouse buttons
  364. int buttonIndex = (int)mouseButton - StartIndexOfMouseButtons;
  365. InputState oldState = buttons[buttonIndex];
  366. // Is mouse button pressed and is not released yet?
  367. if (wasMouseDown &&
  368. wasMouseUp == false)
  369. {
  370. // Was button pressed before, then just keep it pressed.
  371. if (oldState >= InputState.Pressed)
  372. {
  373. buttons[buttonIndex] = InputState.IsPressed;
  374. }
  375. // Else set it to PressBegin (no matter if it was NotPressed or
  376. // PressEnd before).
  377. else
  378. {
  379. buttons[buttonIndex] = InputState.Pressed;
  380. }
  381. } // if
  382. // Was the mouse pressed and released this frame?
  383. else if (wasMouseDown &&
  384. wasMouseUp)
  385. {
  386. // Then set the state immediately to PressEnd to handle this.
  387. buttons[buttonIndex] = InputState.Released;
  388. }
  389. else if (wasMouseDown == false &&
  390. wasMouseUp)
  391. {
  392. // Button is not pressed, if it was before, disable it!
  393. if (oldState >= InputState.Pressed)
  394. {
  395. buttons[buttonIndex] = InputState.Released;
  396. }
  397. // If it was just released (or already not pressed), set it to unused
  398. else
  399. {
  400. buttons[buttonIndex] = InputState.NotPressed;
  401. }
  402. } // else
  403. else
  404. {
  405. // Else nothing changed, just update PressBegin and PressEnd states!
  406. if (oldState == InputState.Pressed)
  407. {
  408. buttons[buttonIndex] = InputState.IsPressed;
  409. }
  410. else if (oldState == InputState.Released)
  411. {
  412. buttons[buttonIndex] = InputState.NotPressed;
  413. }
  414. }
  415. }
  416. #endregion
  417. #region SetPosition
  418. /// <summary>
  419. /// Set mouse position in pixel space natively.
  420. /// </summary>
  421. /// <param name="newPixelPosition">New mouse position</param>
  422. protected override void SetPosition(Point newPixelPosition)
  423. {
  424. // We need to add the viewport offset to the pixel position and unlike
  425. // XnaMouse we also need to add the window offset itself!
  426. newPixelPosition += DeltaApp.Window.ViewportOffset +
  427. DeltaApp.Window.Location;
  428. Cursor.Position = new System.Drawing.Point((int)newPixelPosition.X,
  429. (int)newPixelPosition.Y);
  430. }
  431. #endregion
  432. #region Update
  433. /// <summary>
  434. /// Update
  435. /// </summary>
  436. protected override void Update()
  437. {
  438. // No need to update if we have nothing connected. Same goes if we had
  439. // no data before and still got nothing new, we can return right here.
  440. if (IsConnected == false ||
  441. HasData == false)
  442. {
  443. return;
  444. }
  445. // Update the mouseMovement, how much was the mouse moved this frame
  446. //mouseMovement = thisFrameMousePosition - lastFrameMousePosition;
  447. mouseMovement = thisFrameMouseMovement;
  448. thisFrameMouseMovement = Point.Zero;
  449. // Store the last mouse position.
  450. lastFrameMousePosition = thisFrameMousePosition;
  451. thisFrameMousePosition = mousePosition;
  452. // Update the scroll delta, we only have the total value from the events
  453. mouseScrollDelta = TotalScrollWheel - lastScrollWheelValue;
  454. lastScrollWheelValue = thisFrameScrollWheelValue;
  455. thisFrameScrollWheelValue = TotalScrollWheel;
  456. // Update button states based on the remembered mouse up and down events
  457. UpdateButton(newEventMouseLeftDown, newEventMouseLeftUp,
  458. InputButton.MouseLeft);
  459. UpdateButton(newEventMouseRightDown, newEventMouseRightUp,
  460. InputButton.MouseRight);
  461. UpdateButton(newEventMouseMiddleDown, newEventMouseMiddleUp,
  462. InputButton.MouseMiddle);
  463. UpdateButton(newEventMouseExtraOneDown, newEventMouseExtraOneUp,
  464. InputButton.MouseExtraOne);
  465. UpdateButton(newEventMouseExtraTwoDown, newEventMouseExtraTwoUp,
  466. InputButton.MouseExtraTwo);
  467. // Since all the mouse gesture handling is the same for all frameworks,
  468. // do it in BaseMouse, but give it some hints from the events we caught
  469. UpdateMouseGestures(remClick, remDoubleClick, remDrag);
  470. // Mark that currently no data has been changed, as soon as we receive
  471. // another event, this will be set to true again! Please also note that
  472. // if we have any pending button state or gesture currently active, we
  473. // need to update the next tick too until it is all set to NotPressed
  474. // again.
  475. bool allButtonsNotPressed = true;
  476. for (int num = 0; num < buttons.Length; num++)
  477. {
  478. if (buttons[num] != InputState.NotPressed)
  479. {
  480. allButtonsNotPressed = false;
  481. break;
  482. }
  483. }
  484. if (allButtonsNotPressed &&
  485. mouseMovement != Point.Zero)
  486. {
  487. HasData = false;
  488. }
  489. // Nothing else to do here, everything is handled already by events, but
  490. // reset all event states.
  491. newEventMouseLeftDown = false;
  492. newEventMouseRightDown = false;
  493. newEventMouseMiddleDown = false;
  494. newEventMouseExtraOneDown = false;
  495. newEventMouseExtraTwoDown = false;
  496. newEventMouseLeftUp = false;
  497. newEventMouseRightUp = false;
  498. newEventMouseMiddleUp = false;
  499. newEventMouseExtraOneUp = false;
  500. newEventMouseExtraTwoUp = false;
  501. remClick = false;
  502. remDoubleClick = false;
  503. remDrag = false;
  504. }
  505. #endregion
  506. #endregion
  507. }
  508. }