From fc5a2e5e33157449ec100af2c6f8ee09641f538a Mon Sep 17 00:00:00 2001 From: Ed Swartz Date: Thu, 23 Apr 2015 11:20:24 -0500 Subject: [PATCH] Implement confined mouse mode for OS X. Note: it seems that using #set_pointer() will result in a long delay before an absolute mouse position is updated, even though deltas are reported. Thus, in CocoaPandaView, the mouse deltas are sent for both relative and confined modes, so client code (e.g. the "mouse modes" demo) will be able to recenter the mouse without choppy movement. --- panda/src/cocoadisplay/cocoaGraphicsWindow.mm | 50 +++++++++++++++++-- panda/src/cocoadisplay/cocoaPandaView.mm | 5 +- panda/src/osxdisplay/osxGraphicsWindow.mm | 19 +++++++ samples/mouse-modes/main.py | 10 ++-- 4 files changed, 76 insertions(+), 8 deletions(-) diff --git a/panda/src/cocoadisplay/cocoaGraphicsWindow.mm b/panda/src/cocoadisplay/cocoaGraphicsWindow.mm index efe9849ff5..5feef27535 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsWindow.mm +++ b/panda/src/cocoadisplay/cocoaGraphicsWindow.mm @@ -1022,6 +1022,23 @@ set_properties_now(WindowProperties &properties) { } properties.clear_z_order(); } + + if (properties.has_mouse_mode()) { + switch (properties.get_mouse_mode()) { + case WindowProperties::M_absolute: + case WindowProperties::M_confined: // confined is maintained in mouse move event + CGAssociateMouseAndMouseCursorPosition(true); + _properties.set_mouse_mode(properties.get_mouse_mode()); + properties.clear_mouse_mode(); + break; + + case WindowProperties::M_relative: + CGAssociateMouseAndMouseCursorPosition(false); + _properties.set_mouse_mode(properties.get_mouse_mode()); + properties.clear_mouse_mode(); + break; + } + } } //////////////////////////////////////////////////////////////////// @@ -1670,6 +1687,8 @@ handle_mouse_button_event(int button, bool down) { //////////////////////////////////////////////////////////////////// void CocoaGraphicsWindow:: handle_mouse_moved_event(bool in_window, double x, double y, bool absolute) { + double nx, ny; + if (absolute) { if (cocoadisplay_cat.is_spam()) { if (in_window != _input_devices[0].get_pointer().get_in_window()) { @@ -1682,15 +1701,40 @@ handle_mouse_moved_event(bool in_window, double x, double y, bool absolute) { } // Strangely enough, in Cocoa, mouse Y coordinates are 1-based. - _input_devices[0].set_pointer(in_window, x, y - 1, - ClockObject::get_global_clock()->get_frame_time()); + nx = x; + ny = y - 1; } else { // We received deltas, so add it to the current mouse position. MouseData md = _input_devices[0].get_pointer(); - _input_devices[0].set_pointer_in_window(md.get_x() + x, md.get_y() + y); + nx = md.get_x() + x; + ny = md.get_y() + y; } + if (_properties.get_mouse_mode() == WindowProperties::M_confined + && !in_window) { + CGPoint point; + + nx = std::max(0., std::min((double) get_x_size() - 1, nx)); + ny = std::max(0., std::min((double) get_y_size() - 1, ny)); + + if (_properties.get_fullscreen()) { + point = CGPointMake(nx, ny + 1); + } else { + point = CGPointMake(nx + _properties.get_x_origin(), + ny + _properties.get_y_origin() + 1); + } + + if (CGWarpMouseCursorPosition(point) == kCGErrorSuccess) { + in_window = true; + } else { + cocoadisplay_cat.warning() << "Failed to return mouse pointer to window\n"; + } + } + + _input_devices[0].set_pointer(in_window, nx, ny, + ClockObject::get_global_clock()->get_frame_time()); + if (in_window != _mouse_hidden && _properties.get_cursor_hidden()) { // Hide the cursor if the mouse enters the window, // and unhide it when the mouse leaves the window. diff --git a/panda/src/cocoadisplay/cocoaPandaView.mm b/panda/src/cocoadisplay/cocoaPandaView.mm index bb7d03c832..8fa199e5c7 100644 --- a/panda/src/cocoadisplay/cocoaPandaView.mm +++ b/panda/src/cocoadisplay/cocoaPandaView.mm @@ -116,7 +116,10 @@ NSPoint loc = [self convertPoint:[event locationInWindow] fromView:nil]; BOOL inside = [self mouse:loc inRect:[self bounds]]; - if (_graphicsWindow->get_properties().get_mouse_mode() == WindowProperties::M_relative) { + // the correlation between mouse deltas and location + // are "debounced" apparently, so send deltas for both + // relative and confined modes + if (_graphicsWindow->get_properties().get_mouse_mode() != WindowProperties::M_absolute) { _graphicsWindow->handle_mouse_moved_event(inside, [event deltaX], [event deltaY], false); } else { _graphicsWindow->handle_mouse_moved_event(inside, loc.x, loc.y, true); diff --git a/panda/src/osxdisplay/osxGraphicsWindow.mm b/panda/src/osxdisplay/osxGraphicsWindow.mm index 3b18fd3914..36db5a55ce 100644 --- a/panda/src/osxdisplay/osxGraphicsWindow.mm +++ b/panda/src/osxdisplay/osxGraphicsWindow.mm @@ -2064,6 +2064,25 @@ set_properties_now(WindowProperties &properties) { properties.clear_minimized(); } + if (properties.has_mouse_mode()) { + switch (properties.get_mouse_mode()) { + case WindowProperties::M_absolute: + CGAssociateMouseAndMouseCursorPosition(true); + _properties.set_mouse_mode(WindowProperties::M_absolute); + properties.clear_mouse_mode(); + break; + + case WindowProperties::M_relative: + CGAssociateMouseAndMouseCursorPosition(false); + _properties.set_mouse_mode(WindowProperties::M_relative); + properties.clear_mouse_mode(); + break; + + case WindowProperties::M_confined: + break; + } + } + if (osxdisplay_cat.is_debug()) { osxdisplay_cat.debug() << "set_properties_now Out....." << _properties << "\n"; diff --git a/samples/mouse-modes/main.py b/samples/mouse-modes/main.py index 50e81f550e..75ddbf08ea 100644 --- a/samples/mouse-modes/main.py +++ b/samples/mouse-modes/main.py @@ -34,7 +34,8 @@ class App(ShowBase): # Disable the camera trackball controls. self.disableMouse() - self.mouseMagnitude = 144 + # control mapping of mouse movement to box movement + self.mouseMagnitude = 1 self.rotateX, self.rotateY = 0, 0 @@ -146,7 +147,8 @@ class App(ShowBase): if self.manualRecenterMouse: # move mouse back to center self.recenterMouse() - + self.lastMouseX, self.lastMouseY = 0, 0 + # scale position and delta to pixels for user w, h = self.win.getSize() @@ -158,8 +160,8 @@ class App(ShowBase): int(dx*w), int(dy*h))) # rotate box by delta - self.rotateX += dx * 10 - self.rotateY += dy * 10 + self.rotateX += dx * 10 * self.mouseMagnitude + self.rotateY += dy * 10 * self.mouseMagnitude self.positionText.setText("Model rotation: {0}, {1}".format( int(self.rotateX*1000)/1000., int(self.rotateY*1000)/1000.))