/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
- using System;
- using System.Windows.Forms;
- using Delta.Engine;
- using Delta.InputSystem.Devices;
- using Delta.Platforms.Windows;
- using Delta.Utilities.Datatypes;
- using DeltaApp = Delta.Engine.Application;
- using DeltaScreen = Delta.Engine.ScreenSpace;
- using WPFMouseWheelEventArgs = System.Windows.Input.MouseWheelEventArgs;
-
- namespace Delta.InputSystem.Windows
- {
- /// <summary>
- /// Windows mouse implementation, which is based on windows forms events
- /// unlike XnaMouse, which is based on XNA Mouse.GetState polling. The
- /// advantage of using events is that we will able to detect more clicks
- /// in low frame situations. The disadvantage is that this will only work
- /// on Windows (and platforms capable of System.Windows.Forms like Linux
- /// and Mac via the Mono framework). The Windows input device classes should
- /// always be used for editors because they only work on Windows anyways
- /// and using events is great for editors (good for low frame rates and
- /// more accurate, it is even give us better performance than XnaMouse, but
- /// most importantly the WindowsKeyboard implementation is so much better
- /// for Editors because it catches all keyboard key presses, while
- /// XnaKeyboard misses some from time to time due to polling).
- /// </summary>
- public class WindowsMouse : BaseMouse
- {
- #region IsConnected (Public)
- /// <summary>
- /// Is connected, will always return true because this assembly will only
- /// be able to load on Windows platforms anyway.
- /// </summary>
- public override bool IsConnected
- {
- get
- {
- // We only support one mouse via normal Windows events (use MultiMouse
- // for multiple mouses)!
- return Index == 0;
- }
- }
- #endregion
-
- #region Private
-
- #region lastScrollWheelValue (Private)
- /// <summary>
- /// Because we update the TotalScrollWheel value directly based on the
- /// events we receive, calculating the ScrollWheelDelta works a little
- /// different here. We need to check and update it in the Update method.
- /// </summary>
- private float lastScrollWheelValue;
- #endregion
-
- #region newEventMouseLeftDown (Private)
- /// <summary>
- /// Remember the events that happened in order to correctly update each
- /// mouse button in the Update method.
- /// </summary>
- private bool newEventMouseLeftDown;
- #endregion
-
- #region newEventMouseRightDown (Private)
- /// <summary>
- /// Remember the events that happened in order to correctly update each
- /// mouse button in the Update method.
- /// </summary>
- private bool newEventMouseRightDown;
- #endregion
-
- #region newEventMouseMiddleDown (Private)
- /// <summary>
- /// Remember the events that happened in order to correctly update each
- /// mouse button in the Update method.
- /// </summary>
- private bool newEventMouseMiddleDown;
- #endregion
-
- #region newEventMouseExtraOneDown (Private)
- /// <summary>
- /// Remember the events that happened in order to correctly update each
- /// mouse button in the Update method.
- /// </summary>
- private bool newEventMouseExtraOneDown;
- #endregion
-
- #region newEventMouseExtraTwoDown (Private)
- /// <summary>
- /// Remember the events that happened in order to correctly update each
- /// mouse button in the Update method.
- /// </summary>
- private bool newEventMouseExtraTwoDown;
- #endregion
-
- #region newEventMouseLeftUp (Private)
- /// <summary>
- /// Remember the events that happened in order to correctly update each
- /// mouse button in the Update method.
- /// </summary>
- private bool newEventMouseLeftUp;
- #endregion
-
- #region newEventMouseRightUp (Private)
- /// <summary>
- /// Remember the events that happened in order to correctly update each
- /// mouse button in the Update method.
- /// </summary>
- private bool newEventMouseRightUp;
- #endregion
-
- #region newEventMouseMiddleUp (Private)
- /// <summary>
- /// Remember the events that happened in order to correctly update each
- /// mouse button in the Update method.
- /// </summary>
- private bool newEventMouseMiddleUp;
- #endregion
-
- #region newEventMouseExtraOneUp (Private)
- /// <summary>
- /// Remember the events that happened in order to correctly update each
- /// mouse button in the Update method.
- /// </summary>
- private bool newEventMouseExtraOneUp;
- #endregion
-
- #region newEventMouseExtraTwoUp (Private)
- /// <summary>
- /// Remember the events that happened in order to correctly update each
- /// mouse button in the Update method.
- /// </summary>
- private bool newEventMouseExtraTwoUp;
- #endregion
-
- #region remClick (Private)
- /// <summary>
- /// Also remember clicks, double clicks and drag gestures as they happen
- /// in the events (more accurate than to check our simplified states,
- /// this is especially useful for low frame rate situations)!
- /// </summary>
- private bool remClick;
- #endregion
-
- #region remDoubleClick (Private)
- /// <summary>
- /// Also remember clicks, double clicks and drag gestures as they happen
- /// in the events (more accurate than to check our simplified states,
- /// this is especially useful for low frame rate situations)!
- /// </summary>
- private bool remDoubleClick;
- #endregion
-
- #region remDrag (Private)
- /// <summary>
- /// Also remember clicks, double clicks and drag gestures as they happen
- /// in the events (more accurate than to check our simplified states,
- /// this is especially useful for low frame rate situations)!
- /// </summary>
- private bool remDrag;
- #endregion
-
- #region thisFrameMousePosition (Private)
- /// <summary>
- /// Little helper variable to delay the current mouse position for exactly
- /// one frame so we have always a valid "lastFramePosition"
- /// </summary>
- private Point thisFrameMousePosition;
- #endregion
-
- #region thisFrameScrollWheelValue (Private)
- /// <summary>
- /// Little helper variable to delay the current mouse scroll value for
- /// exactly one frame so we have always a valid "lastFramePosition"
- /// </summary>
- private float thisFrameScrollWheelValue;
- #endregion
-
- #region thisFrameMouseMovement (Private)
- private Point thisFrameMouseMovement;
- #endregion
-
- #endregion
-
- #region Constructors
- /// <summary>
- /// Create WindowsMouse, will link up the events from the windows form
- /// to our event handling methods here.
- /// </summary>
- public WindowsMouse()
- {
- // We need to use the viewport handle for mouse input events
- Control viewport = Control.FromHandle(DeltaApp.Window.ViewportHandle);
- viewport.MouseDown += OnMouseDown;
- viewport.MouseUp += OnMouseUp;
- viewport.MouseMove += OnMouseMove;
-
- // Same as for XnaMouse we need to use the windows form for mouse scroll
- // events, they won't be forwarded to the viewport control. Also support
- // WPF (Windows Presentation Foundation) windows here (most editors).
- IntPtr nativeWindowHande = DeltaApp.Window.Handle;
- if (DeltaApp.Window.GetType() == typeof(WindowWPF))
- {
- viewport.MouseWheel += OnFormsWindowMouseWheel;
- }
- // Else this is a normal windows forms window (games and some editors)
- else
- {
- // For Windows Forms we have to ask the window itself, the control
- // will not get the mouse wheel events. Fix this if more advanced
- // editors should not capture all mouse wheel events!
- Control formsWindow = Control.FromHandle(nativeWindowHande);
- formsWindow.MouseWheel += OnFormsWindowMouseWheel;
- } // else
- }
- #endregion
-
- #region Methods (Private)
-
- #region OnMouseDown
- /// <summary>
- /// Handle on mouse down event.
- /// </summary>
- /// <param name="sender">Event sender</param>
- /// <param name="e">Event</param>
- private void OnMouseDown(object sender, MouseEventArgs e)
- {
- // Mark that we have data for the Update method.
- HasData = true;
- // And store some extra states for gestures detection.
- lastClickBeginTimeMs = clickBeginTimeMs;
- clickBeginTimeMs = Time.Milliseconds;
- // If the any mouse button was just beginning to be pressed, remember
- // the mouseStartClickPosition for DragStart (used for 5 gestures)!
- Point pixelPosition = new Point(e.X, e.Y);
- // For better double tap checks, also keep the last click position
- // around (it is not really a double tap if the mouse was moved a lot).
- clickStartPosition = DeltaScreen.ToQuadraticSpace(pixelPosition);
-
- // Remember the newly pressed button state
- if (e.Button == MouseButtons.Left)
- {
- // Is this a double click (can even happen in the same frame, which
- // makes sense if there was a long operation like loading something)?
- if (newEventMouseLeftDown ||
- buttons[(int)(InputButton.MouseLeft - StartIndexOfMouseButtons)] >=
- InputState.Pressed)
- {
- remDoubleClick = true;
- // Note: The MouseEventArgs Clicks property might also be useful, but
- // more for the MouseClick event, which we don't use here.
- } // if
-
- // Else just remember a normal click
- newEventMouseLeftDown = true;
- }
- else if (e.Button == MouseButtons.Right)
- {
- newEventMouseRightDown = true;
- }
- else if (e.Button == MouseButtons.Middle)
- {
- newEventMouseMiddleDown = true;
- }
- else if (e.Button == MouseButtons.XButton1)
- {
- newEventMouseExtraOneDown = true;
- }
- else if (e.Button == MouseButtons.XButton2)
- {
- newEventMouseExtraTwoDown = true;
- }
- }
- #endregion
-
- #region OnMouseUp
- /// <summary>
- /// Handle on mouse up event.
- /// </summary>
- /// <param name="sender">Event sender</param>
- /// <param name="e">Event</param>
- private void OnMouseUp(object sender, MouseEventArgs e)
- {
- // Mark that we have data for the Update method.
- HasData = true;
- // Remember the newly released button state
- if (e.Button == MouseButtons.Left)
- {
- newEventMouseLeftUp = true;
- // Was mouse also pressed this frame?
- if (newEventMouseLeftDown)
- {
- // Then this was a quick click, which we call MouseTap
- remClick = true;
- // And also check if we were dragging!
- remDrag = mousePosition != clickStartPosition;
- }
- }
- else if (e.Button == MouseButtons.Right)
- {
- newEventMouseRightUp = true;
-
- // Was mouse also pressed and dragged this frame?
- if (newEventMouseRightDown &&
- mousePosition != clickStartPosition)
- {
- remDrag = true;
- }
- }
- else if (e.Button == MouseButtons.Middle)
- {
- newEventMouseMiddleUp = true;
-
- // Was mouse also pressed and dragged this frame?
- if (newEventMouseMiddleDown &&
- mousePosition != clickStartPosition)
- {
- remDrag = true;
- }
- }
- else if (e.Button == MouseButtons.XButton1)
- {
- newEventMouseExtraOneUp = true;
- }
- else if (e.Button == MouseButtons.XButton2)
- {
- newEventMouseExtraTwoUp = true;
- }
- }
- #endregion
-
- #region OnMouseMove
- /// <summary>
- /// Handle on mouse move, will just update our mouse position.
- /// Note: If moving this event could occur more often than the frame rate,
- /// but since we only need to update the mousePosition when it changes
- /// this is still faster than doing it every frame.
- /// </summary>
- /// <param name="sender">Event sender</param>
- /// <param name="e">Event</param>
- private void OnMouseMove(object sender, MouseEventArgs e)
- {
- // Mark that we have data for the Update method.
- HasData = true;
- // and get the new mouse position.
- Point pixelPosition = new Point(e.X, e.Y);
- Point lastMousePos = mousePosition;
- mousePosition = DeltaScreen.ToQuadraticSpace(pixelPosition);
-
- thisFrameMouseMovement += mousePosition - lastMousePos;
- }
- #endregion
-
- #region OnFormsWindowMouseWheel
- /// <summary>
- /// On forms window mouse wheel
- /// </summary>
- /// <param name="sender">Event sender</param>
- /// <param name="e">Event</param>
- private void OnFormsWindowMouseWheel(object sender, MouseEventArgs e)
- {
- // Mark that we have data for the Update method.
- HasData = true;
- // Divide by 120.0 to make sure we have the same data as with XnaMouse
- TotalScrollWheel += e.Delta / 120.0f;
- }
- #endregion
-
- #region OnWPFWindowMouseWheel
- /// <summary>
- /// On WPF window mouse wheel
- /// </summary>
- /// <param name="sender">Event sender</param>
- /// <param name="e">Event</param>
- private void OnWPFWindowMouseWheel(object sender,
- WPFMouseWheelEventArgs e)
- {
- // Mark that we have data for the Update method.
- HasData = true;
- //// Save the last scroll wheel value.
- //lastScrollWheelValue = TotalScrollWheel;
- // Divide by 120.0 to make sure we have the same data as with XnaMouse
- TotalScrollWheel += e.Delta / 120.0f;
- }
- #endregion
-
- #region UpdateButton
- /// <summary>
- /// Update mouse button state based on the data remembered from events.
- /// </summary>
- /// <param name="wasMouseDown">Was mouse pressed</param>
- /// <param name="wasMouseUp">Was mouse released</param>
- /// <param name="mouseButton">Mouse button</param>
- private void UpdateButton(bool wasMouseDown, bool wasMouseUp,
- InputButton mouseButton)
- {
- // Only handle valid requests of actual Mouse buttons
- int buttonIndex = (int)mouseButton - StartIndexOfMouseButtons;
- InputState oldState = buttons[buttonIndex];
- // Is mouse button pressed and is not released yet?
- if (wasMouseDown &&
- wasMouseUp == false)
- {
- // Was button pressed before, then just keep it pressed.
- if (oldState >= InputState.Pressed)
- {
- buttons[buttonIndex] = InputState.IsPressed;
- }
- // Else set it to PressBegin (no matter if it was NotPressed or
- // PressEnd before).
- else
- {
- buttons[buttonIndex] = InputState.Pressed;
- }
- } // if
- // Was the mouse pressed and released this frame?
- else if (wasMouseDown &&
- wasMouseUp)
- {
- // Then set the state immediately to PressEnd to handle this.
- buttons[buttonIndex] = InputState.Released;
- }
- else if (wasMouseDown == false &&
- wasMouseUp)
- {
- // Button is not pressed, if it was before, disable it!
- if (oldState >= InputState.Pressed)
- {
- buttons[buttonIndex] = InputState.Released;
- }
- // If it was just released (or already not pressed), set it to unused
- else
- {
- buttons[buttonIndex] = InputState.NotPressed;
- }
- } // else
- else
- {
- // Else nothing changed, just update PressBegin and PressEnd states!
- if (oldState == InputState.Pressed)
- {
- buttons[buttonIndex] = InputState.IsPressed;
- }
- else if (oldState == InputState.Released)
- {
- buttons[buttonIndex] = InputState.NotPressed;
- }
- }
- }
- #endregion
-
- #region SetPosition
- /// <summary>
- /// Set mouse position in pixel space natively.
- /// </summary>
- /// <param name="newPixelPosition">New mouse position</param>
- protected override void SetPosition(Point newPixelPosition)
- {
- // We need to add the viewport offset to the pixel position and unlike
- // XnaMouse we also need to add the window offset itself!
- newPixelPosition += DeltaApp.Window.ViewportOffset +
- DeltaApp.Window.Location;
- Cursor.Position = new System.Drawing.Point((int)newPixelPosition.X,
- (int)newPixelPosition.Y);
- }
- #endregion
-
- #region Update
- /// <summary>
- /// Update
- /// </summary>
- protected override void Update()
- {
- // No need to update if we have nothing connected. Same goes if we had
- // no data before and still got nothing new, we can return right here.
- if (IsConnected == false ||
- HasData == false)
- {
- return;
- }
-
- // Update the mouseMovement, how much was the mouse moved this frame
- //mouseMovement = thisFrameMousePosition - lastFrameMousePosition;
- mouseMovement = thisFrameMouseMovement;
- thisFrameMouseMovement = Point.Zero;
- // Store the last mouse position.
- lastFrameMousePosition = thisFrameMousePosition;
- thisFrameMousePosition = mousePosition;
-
- // Update the scroll delta, we only have the total value from the events
- mouseScrollDelta = TotalScrollWheel - lastScrollWheelValue;
- lastScrollWheelValue = thisFrameScrollWheelValue;
- thisFrameScrollWheelValue = TotalScrollWheel;
-
- // Update button states based on the remembered mouse up and down events
- UpdateButton(newEventMouseLeftDown, newEventMouseLeftUp,
- InputButton.MouseLeft);
- UpdateButton(newEventMouseRightDown, newEventMouseRightUp,
- InputButton.MouseRight);
- UpdateButton(newEventMouseMiddleDown, newEventMouseMiddleUp,
- InputButton.MouseMiddle);
- UpdateButton(newEventMouseExtraOneDown, newEventMouseExtraOneUp,
- InputButton.MouseExtraOne);
- UpdateButton(newEventMouseExtraTwoDown, newEventMouseExtraTwoUp,
- InputButton.MouseExtraTwo);
- // Since all the mouse gesture handling is the same for all frameworks,
- // do it in BaseMouse, but give it some hints from the events we caught
- UpdateMouseGestures(remClick, remDoubleClick, remDrag);
-
- // Mark that currently no data has been changed, as soon as we receive
- // another event, this will be set to true again! Please also note that
- // if we have any pending button state or gesture currently active, we
- // need to update the next tick too until it is all set to NotPressed
- // again.
- bool allButtonsNotPressed = true;
- for (int num = 0; num < buttons.Length; num++)
- {
- if (buttons[num] != InputState.NotPressed)
- {
- allButtonsNotPressed = false;
- break;
- }
- }
- if (allButtonsNotPressed &&
- mouseMovement != Point.Zero)
- {
- HasData = false;
- }
-
- // Nothing else to do here, everything is handled already by events, but
- // reset all event states.
- newEventMouseLeftDown = false;
- newEventMouseRightDown = false;
- newEventMouseMiddleDown = false;
- newEventMouseExtraOneDown = false;
- newEventMouseExtraTwoDown = false;
- newEventMouseLeftUp = false;
- newEventMouseRightUp = false;
- newEventMouseMiddleUp = false;
- newEventMouseExtraOneUp = false;
- newEventMouseExtraTwoUp = false;
- remClick = false;
- remDoubleClick = false;
- remDrag = false;
- }
- #endregion
-
- #endregion
- }
- }