Browse Source

Merge pull request #18 from eswartz/support-mouse-confinement

Support mouse confinement
rdb 10 years ago
parent
commit
5f20afd5ea

+ 4 - 18
panda/src/display/graphicsWindow.cxx

@@ -736,24 +736,10 @@ set_properties_now(WindowProperties &properties) {
     // Fullscreen property specified, but unchanged.
     properties.clear_fullscreen();
   }
-  if (properties.has_mouse_mode() ) {
-    
-    if (properties.get_mouse_mode() == _properties.get_mouse_mode()) {  
-      properties.clear_mouse_mode();
-    }
-    else {
-      if(properties.get_mouse_mode() == WindowProperties::M_absolute) {
-        _properties.set_mouse_mode(WindowProperties::M_absolute);
-        mouse_mode_absolute();
-        properties.clear_mouse_mode();
-      }
-      else
-      {
-        _properties.set_mouse_mode(WindowProperties::M_relative);
-        mouse_mode_relative();
-        properties.clear_mouse_mode();        
-      }    
-    }
+  if (properties.has_mouse_mode() &&
+      properties.get_mouse_mode() == _properties.get_mouse_mode()) {
+    // Mouse mode specified, but unchanged.
+    properties.clear_mouse_mode();
   }
 }
 

+ 23 - 12
panda/src/display/windowProperties.I

@@ -798,19 +798,30 @@ clear_z_order() {
 //     Function: WindowProperties::set_mouse_mode
 //       Access: Published
 //  Description: Specifies the mode in which the window is to operate
-//               its mouse pointer.  The default is M_absolute, which
-//               is the normal mode in which a mouse pointer operates;
-//               but you can also set M_relative, which is
-//               particularly useful for FPS-style mouse movements
-//               where you have hidden the mouse pointer and are are
-//               more interested in how fast the mouse is moving,
-//               rather than precisely where the pointer is hovering.
+//               its mouse pointer.
+//
+//               M_absolute: the normal mode in which a mouse pointer
+//               operates, where the mouse can move outside the window
+//               and the mouse coordinates are relative to its
+//               position in the window.
+//
+//               M_relative (OSX or Unix/X11 only): a mode where only
+//               relative movements are reported; particularly useful
+//               for FPS-style mouse movements where you have hidden
+//               the mouse pointer and are are more interested in how
+//               fast the mouse is moving, rather than precisely where
+//               the pointer is hovering.
+//
+//               This has no effect on Windows.  On Unix/X11, this
+//               requires the Xxf86dga extension to be available.
+//
+//               M_confined: this mode reports absolute mouse
+//               positions, but confines the mouse pointer to
+//               the window boundary.  It can portably replace
+//               M_relative for an FPS, but you need to periodically
+//               move the pointer to the center of the window
+//               and track movement deltas.
 //
-//               This has no effect on Windows, which does not
-//               have this concept; but is important to do on OSX
-//               and Unix/X11 to properly enable a smooth FPS-style
-//               mouselook mode.  On Unix/X11, this requires the
-//               Xxf86dga extension to be available.
 ////////////////////////////////////////////////////////////////////
 INLINE void WindowProperties::
 set_mouse_mode(MouseMode mode) {

+ 4 - 0
panda/src/display/windowProperties.cxx

@@ -400,6 +400,8 @@ operator << (ostream &out, WindowProperties::MouseMode mode) {
     return out << "absolute";
   case WindowProperties::M_relative:
     return out << "relative";
+  case WindowProperties::M_confined:
+    return out << "confined";
   }
   return out << "**invalid WindowProperties::MouseMode(" << (int)mode << ")**";
 }
@@ -413,6 +415,8 @@ operator >> (istream &in, WindowProperties::MouseMode &mode) {
     mode = WindowProperties::M_absolute;
   } else if (word == "relative") {
     mode = WindowProperties::M_relative;
+  } else if (word == "confined") {
+    mode = WindowProperties::M_confined;
   } else {
     display_cat.warning()
       << "Unknown mouse mode: " << word << "\n";

+ 1 - 0
panda/src/display/windowProperties.h

@@ -40,6 +40,7 @@ PUBLISHED:
   enum MouseMode {
     M_absolute,
     M_relative,
+    M_confined,
   };
 
   WindowProperties();

+ 44 - 0
panda/src/windisplay/winGraphicsWindow.cxx

@@ -35,6 +35,8 @@ WinGraphicsWindow *WinGraphicsWindow::_creating_window = NULL;
 WinGraphicsWindow *WinGraphicsWindow::_cursor_window = NULL;
 bool WinGraphicsWindow::_cursor_hidden = false;
 
+RECT WinGraphicsWindow::_mouse_unconfined_cliprect;
+
 // These are used to save the previous state of the fancy Win2000
 // effects that interfere with rendering when the mouse wanders into a
 // window's client area.
@@ -359,6 +361,48 @@ set_properties_now(WindowProperties &properties) {
       }
     }
   }
+
+  if (properties.has_mouse_mode()) {
+    if (properties.get_mouse_mode() != _properties.get_mouse_mode()) {
+      switch (properties.get_mouse_mode()) {
+      case WindowProperties::M_absolute:
+      case WindowProperties::M_relative:    // not implemented, treat as absolute
+
+        if (_properties.get_mouse_mode() == WindowProperties::M_confined) {
+          ClipCursor(NULL);
+          windisplay_cat.info() << "Unconfining cursor from window\n";
+        }
+        _properties.set_mouse_mode(WindowProperties::M_absolute);
+        break;
+
+      case WindowProperties::M_confined:
+        {
+          RECT clip;
+
+          if (!GetWindowRect(_hWnd, &clip)) {
+            windisplay_cat.warning()
+                << "GetWindowRect() failed in set_properties_now.  Cannot confine cursor.\n";
+          } else {
+            windisplay_cat.info()
+                    << "ClipCursor() to " << clip.left << "," << clip.top << " to "
+                    << clip.right << "," << clip.bottom << endl;
+
+            GetClipCursor(&_mouse_unconfined_cliprect);
+            if (!ClipCursor(&clip)) {
+              windisplay_cat.warning()
+                      << "ClipCursor() failed in set_properties_now.  Ignoring.\n";
+            } else {
+              _properties.set_mouse_mode(WindowProperties::M_confined);
+              windisplay_cat.info() << "Confining cursor to window\n";
+            }
+          }
+        }
+        break;
+      }
+    }
+    properties.clear_mouse_mode();
+  }
+
 }
 
 ////////////////////////////////////////////////////////////////////

+ 3 - 0
panda/src/windisplay/winGraphicsWindow.h

@@ -214,6 +214,9 @@ private:
   static BOOL _saved_cursor_shadow;
   static BOOL _saved_mouse_vanish;
 
+  // The mouse constraints before applying mouse mode M_confined.
+  static RECT _mouse_unconfined_cliprect;
+
   // Since the Panda API requests icons and cursors by filename, we
   // need a table mapping filenames to handles, so we can avoid
   // re-reading the file each time we change icons.

+ 84 - 44
panda/src/x11display/x11GraphicsWindow.cxx

@@ -711,6 +711,88 @@ set_properties_now(WindowProperties &properties) {
     properties.clear_foreground();
   }
 
+  if (properties.has_mouse_mode()) {
+    switch (properties.get_mouse_mode()) {
+    case WindowProperties::M_absolute:
+      XUngrabPointer(_display, CurrentTime);
+#ifdef HAVE_XF86DGA
+      if (_dga_mouse_enabled) {
+        x11display_cat.info() << "Disabling relative mouse using XF86DGA extension\n";
+        XF86DGADirectVideo(_display, _screen, 0);
+        _dga_mouse_enabled = false;
+      }
+#endif
+      _properties.set_mouse_mode(WindowProperties::M_absolute);
+      properties.clear_mouse_mode();
+      break;
+
+    case WindowProperties::M_relative:
+#ifdef HAVE_XF86DGA
+      if (!_dga_mouse_enabled) {
+        int major_ver, minor_ver;
+        if (XF86DGAQueryVersion(_display, &major_ver, &minor_ver)) {
+
+          X11_Cursor cursor = None;
+          if (_properties.get_cursor_hidden()) {
+            x11GraphicsPipe *x11_pipe;
+            DCAST_INTO_V(x11_pipe, _pipe);
+            cursor = x11_pipe->get_hidden_cursor();
+          }
+
+          if (XGrabPointer(_display, _xwindow, True, 0, GrabModeAsync,
+              GrabModeAsync, _xwindow, cursor, CurrentTime) != GrabSuccess) {
+            x11display_cat.error() << "Failed to grab pointer!\n";
+          } else {
+            x11display_cat.info() << "Enabling relative mouse using XF86DGA extension\n";
+            XF86DGADirectVideo(_display, _screen, XF86DGADirectMouse);
+
+            _properties.set_mouse_mode(WindowProperties::M_relative);
+            properties.clear_mouse_mode();
+            _dga_mouse_enabled = true;
+
+            // Get the real mouse position, so we can add/subtract
+            // our relative coordinates later.
+            XEvent event;
+            XQueryPointer(_display, _xwindow, &event.xbutton.root,
+              &event.xbutton.window, &event.xbutton.x_root, &event.xbutton.y_root,
+              &event.xbutton.x, &event.xbutton.y, &event.xbutton.state);
+            _input_devices[0].set_pointer_in_window(event.xbutton.x, event.xbutton.y);
+          }
+        } else {
+          x11display_cat.info() << "XF86DGA extension not available\n";
+          _dga_mouse_enabled = false;
+        }
+      }
+#endif
+      break;
+
+    case WindowProperties::M_confined:
+      {
+#ifdef HAVE_XF86DGA
+        if (_dga_mouse_enabled) {
+          XF86DGADirectVideo(_display, _screen, 0);
+          _dga_mouse_enabled = false;
+        }
+#endif
+        X11_Cursor cursor = None;
+        if (_properties.get_cursor_hidden()) {
+          x11GraphicsPipe *x11_pipe;
+          DCAST_INTO_V(x11_pipe, _pipe);
+          cursor = x11_pipe->get_hidden_cursor();
+        }
+
+        if (XGrabPointer(_display, _xwindow, True, 0, GrabModeAsync,
+            GrabModeAsync, _xwindow, cursor, CurrentTime) != GrabSuccess) {
+          x11display_cat.error() << "Failed to grab pointer!\n";
+        } else {
+          _properties.set_mouse_mode(WindowProperties::M_confined);
+          properties.clear_mouse_mode();
+        }
+      }
+      break;
+    }
+  }
+
   set_wm_properties(wm_properties, true);
 }
 
@@ -721,14 +803,7 @@ set_properties_now(WindowProperties &properties) {
 ////////////////////////////////////////////////////////////////////
 void x11GraphicsWindow::
 mouse_mode_absolute() {
-#ifdef HAVE_XF86DGA
-  if (!_dga_mouse_enabled) return;
-
-  XUngrabPointer(_display, CurrentTime);
-  x11display_cat.info() << "Disabling relative mouse using XF86DGA extension\n";
-  XF86DGADirectVideo(_display, _screen, 0);
-  _dga_mouse_enabled = false;
-#endif
+  // unused: remove in 1.10!
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -738,42 +813,7 @@ mouse_mode_absolute() {
 ////////////////////////////////////////////////////////////////////
 void x11GraphicsWindow::
 mouse_mode_relative() {
-#ifdef HAVE_XF86DGA
-  if (_dga_mouse_enabled) return;
-
-  int major_ver, minor_ver;
-  if (XF86DGAQueryVersion(_display, &major_ver, &minor_ver)) {
-
-    X11_Cursor cursor = None;
-    if (_properties.get_cursor_hidden()) {
-      x11GraphicsPipe *x11_pipe;
-      DCAST_INTO_V(x11_pipe, _pipe);
-      cursor = x11_pipe->get_hidden_cursor();
-    }
-
-    if (XGrabPointer(_display, _xwindow, True, 0, GrabModeAsync,
-        GrabModeAsync, _xwindow, cursor, CurrentTime) != GrabSuccess) {
-      x11display_cat.error() << "Failed to grab pointer!\n";
-      return;
-    }
-
-    x11display_cat.info() << "Enabling relative mouse using XF86DGA extension\n";
-    XF86DGADirectVideo(_display, _screen, XF86DGADirectMouse);
-
-    _dga_mouse_enabled = true;
-  } else {
-    x11display_cat.info() << "XF86DGA extension not available\n";
-    _dga_mouse_enabled = false;
-  }
-
-  // Get the real mouse position, so we can add/subtract
-  // our relative coordinates later.
-  XEvent event;
-  XQueryPointer(_display, _xwindow, &event.xbutton.root,
-    &event.xbutton.window, &event.xbutton.x_root, &event.xbutton.y_root,
-    &event.xbutton.x, &event.xbutton.y, &event.xbutton.state);
-  _input_devices[0].set_pointer_in_window(event.xbutton.x, event.xbutton.y);
-#endif
+  // unused: remove in 1.10!
 }
 
 ////////////////////////////////////////////////////////////////////

+ 172 - 0
samples/mouse-modes/main.py

@@ -0,0 +1,172 @@
+#!/usr/bin/env python
+'''
+Demonstrate different mouse modes
+'''
+
+# from panda3d.core import loadPrcFileData
+# 
+# loadPrcFileData("", "notify-level-x11display debug") 
+# loadPrcFileData("", "notify-level-windisplay debug") 
+#
+# loadPrcFileData("", "load-display p3tinydisplay") 
+
+from panda3d.core import WindowProperties, TextNode
+from direct.task.TaskManagerGlobal import taskMgr
+from direct.gui.OnscreenText import OnscreenText
+from direct.task import Task
+from direct.showbase.ShowBase import ShowBase
+
+import sys
+
+class App(ShowBase):
+    def __init__(self):
+        ShowBase.__init__(self)
+        self.base = self
+        self.setup()
+
+    def genLabelText(self, text, i):
+        text = OnscreenText(text = text, pos = (-1.3, .5-.05*i), fg=(0,1,0,1),
+                      align = TextNode.ALeft, scale = .05)
+        return text
+    
+        
+    def setup(self):
+        # Disable the camera trackball controls.
+        self.disableMouse()
+        
+        self.mouseMagnitude = 144
+
+        self.rotateX, self.rotateY = 0, 0
+
+        self.genLabelText("[0] Absolute mode, [1] Relative mode, [2] Confined mode", 0)      
+        
+        self.base.accept('0', lambda: self.setMouseMode(WindowProperties.M_absolute))       
+        self.base.accept('1', lambda: self.setMouseMode(WindowProperties.M_relative))       
+        self.base.accept('2', lambda: self.setMouseMode(WindowProperties.M_confined))
+        
+        self.genLabelText("[C] Manually re-center mouse on each tick", 1)      
+        self.base.accept('C', lambda: self.toggleRecenter())
+        self.base.accept('c', lambda: self.toggleRecenter())
+               
+        self.genLabelText("[S] Show mouse", 2)      
+        self.base.accept('S', lambda: self.toggleMouse())
+        self.base.accept('s', lambda: self.toggleMouse())
+               
+        self.base.accept('escape', sys.exit, [0])       
+        
+        self.mouseText = self.genLabelText("", 5)      
+        self.deltaText = self.genLabelText("", 6)      
+        self.positionText = self.genLabelText("", 8)
+        
+        self.lastMouseX, self.lastMouseY = None, None
+        
+        self.hideMouse = False
+        
+        self.setMouseMode(WindowProperties.M_absolute)
+        self.manualRecenterMouse = True
+
+        # make a box to move with the mouse
+        self.model = self.loader.loadModel("box.egg")
+        self.model.reparentTo(self.render)
+        
+        self.cam.setPos(0, -5, 0)
+        self.cam.lookAt(0, 0, 0)
+        
+        self.mouseTask = taskMgr.add(self.mouseTask, "Mouse Task")
+        
+    def setMouseMode(self, mode):
+        print "Changing mode to",mode
+        
+        self.mouseMode = mode
+        
+        wp = WindowProperties()
+        wp.setMouseMode(mode)
+        self.base.win.requestProperties(wp)
+        
+        # these changes may require a tick to apply
+        self.base.taskMgr.doMethodLater(0, self.resolveMouse, "Resolve mouse setting")
+        
+    def resolveMouse(self, t):
+        wp = self.base.win.getProperties()
+        
+        actualMode = wp.getMouseMode()
+        if self.mouseMode != actualMode:
+            print "ACTUAL MOUSE MODE:", actualMode
+            
+        self.mouseMode = actualMode
+        
+        self.rotateX, self.rotateY = -.5, -.5
+        self.lastMouseX, self.lastMouseY = None, None
+        self.recenterMouse()
+
+    def recenterMouse(self):
+        self.base.win.movePointer(0, 
+              int(self.base.win.getProperties().getXSize() / 2),
+              int(self.base.win.getProperties().getYSize() / 2))
+            
+
+    def toggleRecenter(self):
+        print "Toggling re-center behavior"
+        self.manualRecenterMouse = not self.manualRecenterMouse
+        
+    def toggleMouse(self):
+        print "Toggling mouse visibility"
+
+        self.hideMouse = not self.hideMouse
+        
+        wp = WindowProperties()
+        wp.setCursorHidden(self.hideMouse)
+        self.base.win.requestProperties(wp)
+        
+    def mouseTask(self, task):
+        mw = self.base.mouseWatcherNode
+
+        hasMouse = mw.hasMouse()
+        if hasMouse:
+            # get the window manager's idea of the mouse position
+            x, y = mw.getMouseX(), mw.getMouseY()
+            
+            if self.lastMouseX is not None:
+                # get the delta
+                if self.manualRecenterMouse:
+                    # when recentering, the position IS the delta
+                    # since the center is reported as 0, 0
+                    dx, dy = x, y 
+                else:
+                    dx, dy = x - self.lastMouseX, y - self.lastMouseY
+            else:
+                # no data to compare with yet
+                dx, dy = 0, 0
+                 
+            self.lastMouseX, self.lastMouseY = x, y
+                
+        else:
+            x, y, dx, dy = 0, 0, 0, 0
+
+        if self.manualRecenterMouse:
+            # move mouse back to center
+            self.recenterMouse()             
+
+        # scale position and delta to pixels for user
+        w, h = self.win.getSize()
+        
+        self.mouseText.setText("Mode: {0}, Recenter: {1}  |  Mouse: {2}, {3}  |  hasMouse: {4}".format(
+             self.mouseMode, self.manualRecenterMouse,
+             int(x*w), int(y*h), 
+             hasMouse))             
+        self.deltaText.setText("Delta: {0}, {1}".format(
+             int(dx*w), int(dy*h))) 
+
+        # rotate box by delta
+        self.rotateX += dx * 10
+        self.rotateY += dy * 10
+
+        self.positionText.setText("Model rotation: {0}, {1}".format(
+             int(self.rotateX*1000)/1000., int(self.rotateY*1000)/1000.))        
+        
+        self.model.setH(self.rotateX)
+        self.model.setP(self.rotateY)
+        return Task.cont
+    
+app = App()
+app.run()