PageRenderTime 88ms CodeModel.GetById 62ms app.highlight 18ms RepoModel.GetById 1ms app.codeStats 0ms

/InputSystem/Windows/WindowsMouse.cs

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