[Win32] Fix spurious mouse enter and leave messages
authorFraser Waters <frassle@gmail.com>
Sat, 17 Oct 2015 15:56:35 +0000 (16:56 +0100)
committerFraser Waters <frassle@gmail.com>
Sat, 17 Oct 2015 16:00:41 +0000 (17:00 +0100)
Mouse tracking and mouse capture didn't work well together. Mouse capture was
also buggy in that it could release capture prematurely.

Mouse capture is now counted and tracked better, multiple requests to set
capture will only call SetCapture once. ReleaseCapture will only be called once
the same number of releases have been made as sets.
MW_MOUSELEAVE messages are now ignored if the mouse is captured.
Mouse tracking is renabled when mouse capture is released.
While the mouse is captured enter and leave events are genereated based on
tracking of the mouse inside MouseMove.

Fixes #301

Source/OpenTK/Platform/Windows/WinGLNative.cs

index 1d24322..f486d89 100644 (file)
@@ -70,6 +70,7 @@ namespace OpenTK.Platform.Windows
         bool borderless_maximized_window_state = false; // Hack to get maximized mode with hidden border (not normally possible).
         bool focused;
         bool mouse_outside_window = true;
+        int mouse_capture_count = 0;
         int mouse_last_timestamp = 0;
         bool invisible_since_creation; // Set by WindowsMessage.CREATE and consumed by Visible = true (calls BringWindowToFront).
         int suppress_resize; // Used in WindowBorder and WindowState in order to avoid rapid, consecutive resize events.
@@ -384,6 +385,14 @@ namespace OpenTK.Platform.Windows
             return null;
         }
 
+        private void HandleCaptureChanged(IntPtr handle, WindowMessage message, IntPtr wParam, IntPtr lParam)
+        {
+            if (lParam != window.Handle)
+            {
+                mouse_capture_count = 0;
+            }
+        }
+
         void HandleChar(IntPtr handle, WindowMessage message, IntPtr wParam, IntPtr lParam)
         {
             char c;
@@ -400,12 +409,40 @@ namespace OpenTK.Platform.Windows
 
         void HandleMouseMove(IntPtr handle, WindowMessage message, IntPtr wParam, IntPtr lParam)
         {
-            unsafe
+            Point point = new Point(
+                (short)((uint)lParam.ToInt32() & 0x0000FFFF),
+                (short)(((uint)lParam.ToInt32() & 0xFFFF0000) >> 16));
+
+            if (mouse_capture_count > 0)
             {
-                Point point = new Point(
-                    (short)((uint)lParam.ToInt32() & 0x0000FFFF),
-                    (short)(((uint)lParam.ToInt32() & 0xFFFF0000) >> 16));
+                bool mouse_was_outside_window = mouse_outside_window;
+                mouse_outside_window = !ClientRectangle.Contains(point);
 
+                if (mouse_outside_window && !mouse_was_outside_window)
+                {
+                    // Mouse leaving
+                    // If we have mouse capture we ignore WM_MOUSELEAVE events, so 
+                    // have to manually call OnMouseLeave here.
+                    // Mouse tracking is disabled automatically by the OS
+                    OnMouseLeave(EventArgs.Empty);
+                } 
+                else if (!mouse_outside_window && mouse_was_outside_window)
+                {
+                    // Mouse entring
+                    OnMouseEnter(EventArgs.Empty);
+                }
+            }
+            else if (/* !mouse_is_captured && */ mouse_outside_window)
+            {
+                // Once we receive a mouse move event, it means that the mouse has
+                // re-entered the window.
+                mouse_outside_window = false;
+                EnableMouseTracking();
+                OnMouseEnter(EventArgs.Empty);
+            }
+
+            unsafe
+            {
                 // GetMouseMovePointsEx works with screen coordinates
                 Point screenPoint = point;
                 Functions.ClientToScreen(handle, ref screenPoint);
@@ -479,24 +516,18 @@ namespace OpenTK.Platform.Windows
                 }
                 mouse_last_timestamp = timestamp;
             }
-
-            if (mouse_outside_window)
-            {
-                // Once we receive a mouse move event, it means that the mouse has
-                // re-entered the window.
-                mouse_outside_window = false;
-                EnableMouseTracking();
-
-                OnMouseEnter(EventArgs.Empty);
-            }
         }
 
         void HandleMouseLeave(IntPtr handle, WindowMessage message, IntPtr wParam, IntPtr lParam)
         {
-            mouse_outside_window = true;
-            // Mouse tracking is disabled automatically by the OS
-
-            OnMouseLeave(EventArgs.Empty);
+            // If the mouse is captured we get spurious MOUSELEAVE events.
+            // So ignore WM_MOUSELEAVE if capture count != 0.
+            if (mouse_capture_count == 0 )
+            {
+                mouse_outside_window = true;
+                // Mouse tracking is disabled automatically by the OS
+                OnMouseLeave(EventArgs.Empty);
+            }
         }
 
         void HandleMouseWheel(IntPtr handle, WindowMessage message, IntPtr wParam, IntPtr lParam)
@@ -515,25 +546,25 @@ namespace OpenTK.Platform.Windows
 
         void HandleLButtonDown(IntPtr handle, WindowMessage message, IntPtr wParam, IntPtr lParam)
         {
-            Functions.SetCapture(window.Handle);
+            SetCapture();
             OnMouseDown(MouseButton.Left);
         }
 
         void HandleMButtonDown(IntPtr handle, WindowMessage message, IntPtr wParam, IntPtr lParam)
         {
-            Functions.SetCapture(window.Handle);
+            SetCapture();
             OnMouseDown(MouseButton.Middle);
         }
 
         void HandleRButtonDown(IntPtr handle, WindowMessage message, IntPtr wParam, IntPtr lParam)
         {
-            Functions.SetCapture(window.Handle);
+            SetCapture();
             OnMouseDown(MouseButton.Right);
         }
 
         void HandleXButtonDown(IntPtr handle, WindowMessage message, IntPtr wParam, IntPtr lParam)
         {
-            Functions.SetCapture(window.Handle);
+            SetCapture();
             MouseButton button =
                 ((wParam.ToInt32() & 0xFFFF0000) >> 16) == 1 ?
                 MouseButton.Button1 : MouseButton.Button2;
@@ -542,25 +573,25 @@ namespace OpenTK.Platform.Windows
 
         void HandleLButtonUp(IntPtr handle, WindowMessage message, IntPtr wParam, IntPtr lParam)
         {
-            Functions.ReleaseCapture();
+            ReleaseCapture();
             OnMouseUp(MouseButton.Left);
         }
 
         void HandleMButtonUp(IntPtr handle, WindowMessage message, IntPtr wParam, IntPtr lParam)
         {
-            Functions.ReleaseCapture();
+            ReleaseCapture();
             OnMouseUp(MouseButton.Middle);
         }
 
         void HandleRButtonUp(IntPtr handle, WindowMessage message, IntPtr wParam, IntPtr lParam)
         {
-            Functions.ReleaseCapture();
+            ReleaseCapture();
             OnMouseUp(MouseButton.Right);
         }
 
         void HandleXButtonUp(IntPtr handle, WindowMessage message, IntPtr wParam, IntPtr lParam)
         {
-            Functions.ReleaseCapture();
+            ReleaseCapture();
             MouseButton button =
                 ((wParam.ToInt32() & 0xFFFF0000) >> 16) == 1 ?
                 MouseButton.Button1 : MouseButton.Button2;
@@ -694,6 +725,10 @@ namespace OpenTK.Platform.Windows
                     result = HandleSetCursor(handle, message, wParam, lParam);
                     break;
 
+                case WindowMessage.CAPTURECHANGED:
+                    HandleCaptureChanged(handle, message, wParam, lParam);
+                    break;
+
                 #endregion
 
                 #region Input events
@@ -794,6 +829,26 @@ namespace OpenTK.Platform.Windows
             }
         }
 
+        private void SetCapture()
+        {
+            if (mouse_capture_count == 0)
+            {
+                Functions.SetCapture(window.Handle);
+            }
+            mouse_capture_count++;
+        }
+
+        private void ReleaseCapture()
+        {
+            mouse_capture_count--;
+            if (mouse_capture_count == 0)
+            {
+                Functions.ReleaseCapture();
+                // Renable mouse tracking
+                EnableMouseTracking();
+            }
+        }
+
         private void EnableMouseTracking()
         {
             TrackMouseEventStructure me = new TrackMouseEventStructure();