diff --git a/intern/ghost/GHOST_C-api.h b/intern/ghost/GHOST_C-api.h index 9460fb504a9..35391d3f4cf 100644 --- a/intern/ghost/GHOST_C-api.h +++ b/intern/ghost/GHOST_C-api.h @@ -376,7 +376,7 @@ extern GHOST_TSuccess GHOST_SetCursorPosition(GHOST_SystemHandle systemhandle, * @return Indication of success. */ extern GHOST_TSuccess GHOST_SetCursorGrab(GHOST_WindowHandle windowhandle, - int grab); + int grab, int warp); /*************************************************************************************** ** Access to mouse button and keyboard states. diff --git a/intern/ghost/GHOST_IWindow.h b/intern/ghost/GHOST_IWindow.h index ff1484909b0..44ddf9a7cfb 100644 --- a/intern/ghost/GHOST_IWindow.h +++ b/intern/ghost/GHOST_IWindow.h @@ -271,7 +271,7 @@ public: * @param grab The new grab state of the cursor. * @return Indication of success. */ - virtual GHOST_TSuccess setCursorGrab(bool grab) { return GHOST_kSuccess; }; + virtual GHOST_TSuccess setCursorGrab(bool grab, bool warp) { return GHOST_kSuccess; }; }; diff --git a/intern/ghost/intern/GHOST_C-api.cpp b/intern/ghost/intern/GHOST_C-api.cpp index d14945d1bf8..b86c4703ea2 100644 --- a/intern/ghost/intern/GHOST_C-api.cpp +++ b/intern/ghost/intern/GHOST_C-api.cpp @@ -355,11 +355,11 @@ GHOST_TSuccess GHOST_SetCursorPosition(GHOST_SystemHandle systemhandle, GHOST_TSuccess GHOST_SetCursorGrab(GHOST_WindowHandle windowhandle, - int grab) + int grab, int warp) { GHOST_IWindow* window = (GHOST_IWindow*) windowhandle; - return window->setCursorGrab(grab?true:false); + return window->setCursorGrab(grab?true:false, warp?true:false); } diff --git a/intern/ghost/intern/GHOST_SystemX11.cpp b/intern/ghost/intern/GHOST_SystemX11.cpp index cdbdce9c2ca..8c87abf16bc 100644 --- a/intern/ghost/intern/GHOST_SystemX11.cpp +++ b/intern/ghost/intern/GHOST_SystemX11.cpp @@ -374,12 +374,12 @@ GHOST_SystemX11::processEvent(XEvent *xe) // Only generate a single expose event // per read of the event queue. - g_event = new + g_event = new GHOST_Event( getMilliSeconds(), GHOST_kEventWindowUpdate, window - ); + ); } break; } @@ -388,14 +388,42 @@ GHOST_SystemX11::processEvent(XEvent *xe) { XMotionEvent &xme = xe->xmotion; - g_event = new - GHOST_EventCursor( - getMilliSeconds(), - GHOST_kEventCursorMove, - window, - xme.x_root, - xme.y_root - ); + if(window->getCursorWarp()) { + /* Calculate offscreen location and re-center the mouse */ + GHOST_TInt32 x_warp, y_warp, x_new, y_new, x_accum, y_accum; + + window->getCursorWarpPos(x_warp, y_warp); + getCursorPosition(x_new, y_new); + + if(x_warp != x_new || y_warp != y_new) { + window->getCursorWarpAccum(x_accum, y_accum); + x_accum += x_new - x_warp; + y_accum += y_new - y_warp; + + window->setCursorWarpAccum(x_accum, y_accum); + setCursorPosition(x_warp, y_warp); /* reset */ + + g_event = new + GHOST_EventCursor( + getMilliSeconds(), + GHOST_kEventCursorMove, + window, + x_warp + x_accum, + y_warp + y_accum + ); + + } + } + else { + g_event = new + GHOST_EventCursor( + getMilliSeconds(), + GHOST_kEventCursorMove, + window, + xme.x_root, + xme.y_root + ); + } break; } diff --git a/intern/ghost/intern/GHOST_Window.cpp b/intern/ghost/intern/GHOST_Window.cpp index 34e9f519a07..531674607d1 100644 --- a/intern/ghost/intern/GHOST_Window.cpp +++ b/intern/ghost/intern/GHOST_Window.cpp @@ -48,12 +48,16 @@ GHOST_Window::GHOST_Window( : m_drawingContextType(type), m_cursorVisible(true), - m_cursorGrabbed(true), + m_cursorGrabbed(false), + m_cursorWarp(false), m_cursorShape(GHOST_kStandardCursorDefault), m_stereoVisual(stereoVisual) { m_isUnsavedChanges = false; + m_cursorWarpAccumPos[0] = 0; + m_cursorWarpAccumPos[1] = 0; + m_fullScreen = state == GHOST_kWindowStateFullScreen; if (m_fullScreen) { m_fullScreenWidth = width; @@ -94,12 +98,12 @@ GHOST_TSuccess GHOST_Window::setCursorVisibility(bool visible) } } -GHOST_TSuccess GHOST_Window::setCursorGrab(bool grab) +GHOST_TSuccess GHOST_Window::setCursorGrab(bool grab, bool warp) { if(m_cursorGrabbed == grab) return GHOST_kSuccess; - if (setWindowCursorGrab(grab)) { + if (setWindowCursorGrab(grab, warp)) { m_cursorGrabbed = grab; return GHOST_kSuccess; } @@ -150,4 +154,4 @@ GHOST_TSuccess GHOST_Window::setModifiedState(bool isUnsavedChanges) bool GHOST_Window::getModifiedState() { return m_isUnsavedChanges; -} \ No newline at end of file +} diff --git a/intern/ghost/intern/GHOST_Window.h b/intern/ghost/intern/GHOST_Window.h index a2d1675f6ab..36e4bac6dae 100644 --- a/intern/ghost/intern/GHOST_Window.h +++ b/intern/ghost/intern/GHOST_Window.h @@ -158,6 +158,10 @@ public: * @return The visibility state of the cursor. */ inline virtual bool getCursorVisibility() const; + inline virtual bool getCursorWarp() const; + inline virtual bool getCursorWarpPos(GHOST_TInt32 &x, GHOST_TInt32 &y) const; + inline virtual bool getCursorWarpAccum(GHOST_TInt32 &x, GHOST_TInt32 &y) const; + inline virtual bool setCursorWarpAccum(GHOST_TInt32 x, GHOST_TInt32 y); /** * Shows or hides the cursor. @@ -171,7 +175,7 @@ public: * @param grab The new grab state of the cursor. * @return Indication of success. */ - virtual GHOST_TSuccess setCursorGrab(bool grab); + virtual GHOST_TSuccess setCursorGrab(bool grab, bool warp); /** * Sets the window "modified" status, indicating unsaved changes @@ -243,7 +247,7 @@ protected: * Sets the cursor grab on the window using * native window system calls. */ - virtual GHOST_TSuccess setWindowCursorGrab(bool grab) { return GHOST_kSuccess; }; + virtual GHOST_TSuccess setWindowCursorGrab(bool grab, bool warp) { return GHOST_kSuccess; }; /** * Sets the cursor shape on the window using @@ -272,6 +276,15 @@ protected: /** The current grabbed state of the cursor */ bool m_cursorGrabbed; + /** The current warped state of the cursor */ + bool m_cursorWarp; + + /** Initial grab location. */ + GHOST_TInt32 m_cursorWarpInitPos[2]; + + /** Accumulated offset from m_cursorWarpInitPos. */ + GHOST_TInt32 m_cursorWarpAccumPos[2]; + /** The current shape of the cursor */ GHOST_TStandardCursor m_cursorShape; @@ -304,6 +317,42 @@ inline bool GHOST_Window::getCursorVisibility() const return m_cursorVisible; } +inline bool GHOST_Window::getCursorWarp() const +{ + return m_cursorWarp; +} + +inline bool GHOST_Window::getCursorWarpPos(GHOST_TInt32 &x, GHOST_TInt32 &y) const +{ + if(m_cursorWarp==false) + return GHOST_kFailure; + + x= m_cursorWarpInitPos[0]; + y= m_cursorWarpInitPos[1]; + return GHOST_kSuccess; +} + +inline bool GHOST_Window::getCursorWarpAccum(GHOST_TInt32 &x, GHOST_TInt32 &y) const +{ + if(m_cursorWarp==false) + return GHOST_kFailure; + + x= m_cursorWarpAccumPos[0]; + y= m_cursorWarpAccumPos[1]; + return GHOST_kSuccess; +} + +inline bool GHOST_Window::setCursorWarpAccum(GHOST_TInt32 x, GHOST_TInt32 y) +{ + if(m_cursorWarp==false) + return GHOST_kFailure; + + m_cursorWarpAccumPos[0]= x; + m_cursorWarpAccumPos[1]= y; + + return GHOST_kSuccess; +} + inline GHOST_TStandardCursor GHOST_Window::getCursorShape() const { return m_cursorShape; diff --git a/intern/ghost/intern/GHOST_WindowX11.cpp b/intern/ghost/intern/GHOST_WindowX11.cpp index 060e9ca6f6c..c2dc1048ea0 100644 --- a/intern/ghost/intern/GHOST_WindowX11.cpp +++ b/intern/ghost/intern/GHOST_WindowX11.cpp @@ -1400,12 +1400,29 @@ setWindowCursorVisibility( GHOST_TSuccess GHOST_WindowX11:: setWindowCursorGrab( - bool grab + bool grab, bool warp ){ - if(grab) + if(grab) { + if(warp) { + m_system->getCursorPosition(m_cursorWarpInitPos[0], m_cursorWarpInitPos[1]); + + setCursorWarpAccum(0, 0); + setWindowCursorVisibility(false); + m_cursorWarp= true; + } XGrabPointer(m_display, m_window, True, ButtonPressMask| ButtonReleaseMask|PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime); - else + } + else { + if(m_cursorWarp) { /* are we exiting warp */ + setWindowCursorVisibility(true); + /* Almost works without but important otherwise the mouse GHOST location can be incorrect on exit */ + m_system->setCursorPosition(m_cursorWarpInitPos[0], m_cursorWarpInitPos[1]); + + setCursorWarpAccum(0, 0); + m_cursorWarp= false; + } XUngrabPointer(m_display, CurrentTime); + } XFlush(m_display); diff --git a/intern/ghost/intern/GHOST_WindowX11.h b/intern/ghost/intern/GHOST_WindowX11.h index 6f8940bdcbb..08fba3e2be8 100644 --- a/intern/ghost/intern/GHOST_WindowX11.h +++ b/intern/ghost/intern/GHOST_WindowX11.h @@ -252,10 +252,11 @@ protected: /** * Sets the cursor grab on the window using * native window system calls. + * @param warp Only used when grab is enabled, hides the mouse and allows gragging outside the screen. */ GHOST_TSuccess setWindowCursorGrab( - bool grab + bool grab, bool warp ); /** diff --git a/release/scripts/ui/space_userpref.py b/release/scripts/ui/space_userpref.py index 15e6c7ee4be..ce8d03d3292 100644 --- a/release/scripts/ui/space_userpref.py +++ b/release/scripts/ui/space_userpref.py @@ -109,8 +109,8 @@ class USERPREF_PT_view(bpy.types.Panel): sub1.itemL(text="Mouse Wheel:") sub1.itemR(view, "wheel_invert_zoom", text="Invert Zoom") sub1.itemR(view, "wheel_scroll_lines", text="Scroll Lines") - sub1.itemS() - sub1.itemS() + sub1.itemL(text="Mouse Motion:") + sub1.itemR(view, "continuous_mouse", text="Continuous Grab") sub1.itemS() sub1.itemL(text="Menus:") sub1.itemR(view, "open_mouse_over") diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c index bbf8df00b88..385a0eec040 100644 --- a/source/blender/editors/interface/interface_handlers.c +++ b/source/blender/editors/interface/interface_handlers.c @@ -230,6 +230,15 @@ static uiBut *ui_but_last(uiBlock *block) return NULL; } +static int ui_is_a_warp_but(uiBut *but) +{ + if(U.uiflag & USER_CONTINUOUS_MOUSE) + if(ELEM(but->type, NUM, NUMABS)) + return TRUE; + + return FALSE; +} + /* ********************** button apply/revert ************************/ static ListBase UIAfterFuncs = {NULL, NULL}; @@ -1971,6 +1980,51 @@ static int ui_do_but_EXIT(bContext *C, uiBut *but, uiHandleButtonData *data, wmE return WM_UI_HANDLER_CONTINUE; } +/* var names match ui_numedit_but_NUM */ +static float ui_numedit_apply_snapf(float tempf, float softmin, float softmax, float softrange, int snap) +{ + if(tempf==softmin || tempf==softmax) + return tempf; + + switch(snap) { + case 0: + break; + case 1: + if(tempf==softmin || tempf==softmax) { } + else if(softrange < 2.10) tempf= 0.1*floor(10*tempf); + else if(softrange < 21.0) tempf= floor(tempf); + else tempf= 10.0*floor(tempf/10.0); + break; + case 2: + if(tempf==softmin || tempf==softmax) { } + else if(softrange < 2.10) tempf= 0.01*floor(100.0*tempf); + else if(softrange < 21.0) tempf= 0.1*floor(10.0*tempf); + else tempf= floor(tempf); + break; + } + + return tempf; +} + +static float ui_numedit_apply_snap(int temp, float softmin, float softmax, int snap) +{ + if(temp==softmin || temp==softmax) + return temp; + + switch(snap) { + case 0: + break; + case 1: + temp= 10*(temp/10); + break; + case 2: + temp= 100*(temp/100); + break; + } + + return temp; +} + static int ui_numedit_but_NUM(uiBut *but, uiHandleButtonData *data, float fac, int snap, int mx) { float deler, tempf, softmin, softmax, softrange; @@ -1993,74 +2047,88 @@ static int ui_numedit_but_NUM(uiBut *but, uiHandleButtonData *data, float fac, i softmax= but->softmax; softrange= softmax - softmin; - deler= 500; - if(!ui_is_but_float(but)) { - if((softrange)<100) deler= 200.0; - if((softrange)<25) deler= 50.0; - } - deler /= fac; - if(ui_is_but_float(but) && softrange > 11) { - /* non linear change in mouse input- good for high precicsion */ - data->dragf+= (((float)(mx-data->draglastx))/deler) * (fabs(data->dragstartx-mx)*0.002); - } else if (!ui_is_but_float(but) && softrange > 129) { /* only scale large int buttons */ - /* non linear change in mouse input- good for high precicsionm ints need less fine tuning */ - data->dragf+= (((float)(mx-data->draglastx))/deler) * (fabs(data->dragstartx-mx)*0.004); - } else { - /*no scaling */ - data->dragf+= ((float)(mx-data->draglastx))/deler ; - } + if(ui_is_a_warp_but(but)) { + /* Mouse location isn't screen clamped to the screen so use a linear mapping + * 2px == 1-int, or 1px == 1-ClickStep */ + if(ui_is_but_float(but)) { + tempf = data->startvalue + ((mx - data->dragstartx) * fac * 0.01*but->a1); + tempf= ui_numedit_apply_snapf(tempf, softmin, softmax, softrange, snap); + CLAMP(tempf, softmin, softmax); - if(data->dragf>1.0) data->dragf= 1.0; - if(data->dragf<0.0) data->dragf= 0.0; - data->draglastx= mx; - tempf= (softmin + data->dragf*softrange); - - if(!ui_is_but_float(but)) { - temp= floor(tempf+.5); - - if(tempf==softmin || tempf==softmax); - else if(snap) { - if(snap == 2) temp= 100*(temp/100); - else temp= 10*(temp/10); + if(tempf != data->value) { + data->dragchange= 1; + data->value= tempf; + changed= 1; + } } + else { + temp= data->startvalue + (mx - data->dragstartx)/2; /* simple 2px == 1 */ + temp= ui_numedit_apply_snap(temp, softmin, softmax, snap); + CLAMP(temp, softmin, softmax); - CLAMP(temp, softmin, softmax); - lvalue= (int)data->value; - - if(temp != lvalue) { - data->dragchange= 1; - data->value= (double)temp; - changed= 1; + if(temp != data->value) { + data->dragchange= 1; + data->value= temp; + changed= 1; + } } } else { - temp= 0; + /* Use a non-linear mapping of the mouse drag especially for large floats (normal behavior) */ + deler= 500; + if(!ui_is_but_float(but)) { + if((softrange)<100) deler= 200.0; + if((softrange)<25) deler= 50.0; + } + deler /= fac; - if(snap) { - if(snap == 2) { - if(tempf==softmin || tempf==softmax); - else if(softrange < 2.10) tempf= 0.01*floor(100.0*tempf); - else if(softrange < 21.0) tempf= 0.1*floor(10.0*tempf); - else tempf= floor(tempf); - } - else { - if(tempf==softmin || tempf==softmax); - else if(softrange < 2.10) tempf= 0.1*floor(10*tempf); - else if(softrange < 21.0) tempf= floor(tempf); - else tempf= 10.0*floor(tempf/10.0); + if(ui_is_but_float(but) && softrange > 11) { + /* non linear change in mouse input- good for high precicsion */ + data->dragf+= (((float)(mx-data->draglastx))/deler) * (fabs(data->dragstartx-mx)*0.002); + } else if (!ui_is_but_float(but) && softrange > 129) { /* only scale large int buttons */ + /* non linear change in mouse input- good for high precicsionm ints need less fine tuning */ + data->dragf+= (((float)(mx-data->draglastx))/deler) * (fabs(data->dragstartx-mx)*0.004); + } else { + /*no scaling */ + data->dragf+= ((float)(mx-data->draglastx))/deler ; + } + + if(data->dragf>1.0) data->dragf= 1.0; + if(data->dragf<0.0) data->dragf= 0.0; + data->draglastx= mx; + tempf= (softmin + data->dragf*softrange); + + + if(!ui_is_but_float(but)) { + temp= floor(tempf+.5); + + temp= ui_numedit_apply_snap(temp, softmin, softmax, snap); + + CLAMP(temp, softmin, softmax); + lvalue= (int)data->value; + + if(temp != lvalue) { + data->dragchange= 1; + data->value= (double)temp; + changed= 1; } } + else { + temp= 0; + tempf= ui_numedit_apply_snapf(tempf, softmin, softmax, softrange, snap); - CLAMP(tempf, softmin, softmax); + CLAMP(tempf, softmin, softmax); - if(tempf != data->value) { - data->dragchange= 1; - data->value= tempf; - changed= 1; + if(tempf != data->value) { + data->dragchange= 1; + data->value= tempf; + changed= 1; + } } } + return changed; } @@ -2071,7 +2139,10 @@ static int ui_do_but_NUM(bContext *C, uiBlock *block, uiBut *but, uiHandleButton mx= event->x; my= event->y; - ui_window_to_block(data->region, block, &mx, &my); + + if(!ui_is_a_warp_but(but)) { + ui_window_to_block(data->region, block, &mx, &my); + } if(data->state == BUTTON_STATE_HIGHLIGHT) { /* XXX hardcoded keymap check.... */ @@ -3636,11 +3707,15 @@ static void button_activate_state(bContext *C, uiBut *but, uiHandleButtonState s ui_textedit_end(C, but, data); /* number editing */ - if(state == BUTTON_STATE_NUM_EDITING) + if(state == BUTTON_STATE_NUM_EDITING) { + if(ui_is_a_warp_but(but)) + WM_cursor_grab(CTX_wm_window(C), 1, 1); ui_numedit_begin(but, data); - else if(data->state == BUTTON_STATE_NUM_EDITING) + } else if(data->state == BUTTON_STATE_NUM_EDITING) { ui_numedit_end(but, data); - + if(ui_is_a_warp_but(but)) + WM_cursor_grab(CTX_wm_window(C), 0, -1); + } /* menu open */ if(state == BUTTON_STATE_MENU_OPEN) ui_blockopen_begin(C, but, data); diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index 16ab3e1e9bd..edfc4999d80 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -402,6 +402,7 @@ extern UserDef U; /* from blenkernel blender.c */ #define USER_SHOW_FPS (1 << 21) #define USER_MMB_PASTE (1 << 22) #define USER_MENUFIXEDORDER (1 << 23) +#define USER_CONTINUOUS_MOUSE (1 << 24) /* Auto-Keying mode */ /* AUTOKEY_ON is a bitflag */ diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c index 25448d0c2de..1c6bd6515ff 100644 --- a/source/blender/makesrna/intern/rna_userdef.c +++ b/source/blender/makesrna/intern/rna_userdef.c @@ -1655,6 +1655,10 @@ static void rna_def_userdef_view(BlenderRNA *brna) RNA_def_property_boolean_negative_sdna(prop, NULL, "uiflag", USER_MENUFIXEDORDER); RNA_def_property_ui_text(prop, "Contents Follow Opening Direction", "Otherwise menus, etc will always be top to bottom, left to right, no matter opening direction."); + prop= RNA_def_property(srna, "continuous_mouse", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "uiflag", USER_CONTINUOUS_MOUSE); + RNA_def_property_ui_text(prop, "Contents Follow Opening Direction", "Otherwise menus, etc will always be top to bottom, left to right, no matter opening direction."); + prop= RNA_def_property(srna, "global_pivot", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "uiflag", USER_LOCKAROUND); RNA_def_property_ui_text(prop, "Global Pivot", "Lock the same rotation/scaling pivot in all 3D Views."); diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h index 489f27990cd..321afec51b7 100644 --- a/source/blender/windowmanager/WM_api.h +++ b/source/blender/windowmanager/WM_api.h @@ -75,7 +75,7 @@ void WM_cursor_set (struct wmWindow *win, int curs); void WM_cursor_modal (struct wmWindow *win, int curs); void WM_cursor_restore (struct wmWindow *win); void WM_cursor_wait (int val); -void WM_cursor_grab (struct wmWindow *win, int val); +void WM_cursor_grab (struct wmWindow *win, int val, int warp); void WM_timecursor (struct wmWindow *win, int nr); void *WM_paint_cursor_activate(struct wmWindowManager *wm, int (*poll)(struct bContext *C), void (*draw)(struct bContext *C, int, int, void *customdata), void *customdata); diff --git a/source/blender/windowmanager/intern/wm_cursors.c b/source/blender/windowmanager/intern/wm_cursors.c index 56a8d76d8bf..d14cde56083 100644 --- a/source/blender/windowmanager/intern/wm_cursors.c +++ b/source/blender/windowmanager/intern/wm_cursors.c @@ -163,10 +163,10 @@ void WM_cursor_wait(int val) } } -void WM_cursor_grab(wmWindow *win, int val) +void WM_cursor_grab(wmWindow *win, int val, int warp) { if(win) - GHOST_SetCursorGrab(win->ghostwin, val); + GHOST_SetCursorGrab(win->ghostwin, val, warp); } /* afer this you can call restore too */ diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c index c15106e21c9..54841f0e063 100644 --- a/source/blender/windowmanager/intern/wm_event_system.c +++ b/source/blender/windowmanager/intern/wm_event_system.c @@ -442,7 +442,7 @@ static int wm_operator_invoke(bContext *C, wmOperatorType *ot, wmEvent *event, P else if(retval & OPERATOR_RUNNING_MODAL) { /* grab cursor during blocking modal ops (X11) */ if(ot->flag & OPTYPE_BLOCKING) - WM_cursor_grab(CTX_wm_window(C), 1); + WM_cursor_grab(CTX_wm_window(C), 1, (U.uiflag & USER_CONTINUOUS_MOUSE)); } else WM_operator_free(op); @@ -637,8 +637,8 @@ void WM_event_remove_handlers(bContext *C, ListBase *handlers) CTX_wm_region_set(C, region); } + WM_cursor_grab(CTX_wm_window(C), 0, -1); WM_operator_free(handler->op); - WM_cursor_grab(CTX_wm_window(C), 0); } else if(handler->ui_remove) { ScrArea *area= CTX_wm_area(C); @@ -835,7 +835,7 @@ static int wm_handler_operator_call(bContext *C, ListBase *handlers, wmEventHand /* remove modal handler, operator itself should have been cancelled and freed */ if(retval & (OPERATOR_CANCELLED|OPERATOR_FINISHED)) { - WM_cursor_grab(CTX_wm_window(C), 0); + WM_cursor_grab(CTX_wm_window(C), 0, -1); BLI_remlink(handlers, handler); wm_event_free_handler(handler);