Index: src/gfx_type.h =================================================================== --- src/gfx_type.h (revision 27164) +++ src/gfx_type.h (working copy) @@ -138,6 +138,12 @@ bool in_window; ///< mouse inside this window, determines drawing logic bool vehchain; ///< vehicle chain is dragged + + bool UpdateCursorPosition(int x, int y, bool queued_warp); + +private: + bool queued_warp; + Point last_position; }; /** Data about how and where to blit pixels. */ Index: src/video/sdl_v.cpp =================================================================== --- src/video/sdl_v.cpp (revision 27164) +++ src/video/sdl_v.cpp (working copy) @@ -545,20 +545,8 @@ switch (ev.type) { case SDL_MOUSEMOTION: - if (_cursor.fix_at) { - int dx = ev.motion.x - _cursor.pos.x; - int dy = ev.motion.y - _cursor.pos.y; - if (dx != 0 || dy != 0) { - _cursor.delta.x = dx; - _cursor.delta.y = dy; - SDL_CALL SDL_WarpMouse(_cursor.pos.x, _cursor.pos.y); - } - } else { - _cursor.delta.x = ev.motion.x - _cursor.pos.x; - _cursor.delta.y = ev.motion.y - _cursor.pos.y; - _cursor.pos.x = ev.motion.x; - _cursor.pos.y = ev.motion.y; - _cursor.dirty = true; + if (_cursor.UpdateCursorPosition(ev.motion.x, ev.motion.y, true)) { + SDL_CALL SDL_WarpMouse(_cursor.pos.x, _cursor.pos.y); } HandleMouseEvents(); break; Index: src/video/win32_v.cpp =================================================================== --- src/video/win32_v.cpp (revision 27164) +++ src/video/win32_v.cpp (working copy) @@ -747,25 +747,11 @@ SetTimer(hwnd, TID_POLLMOUSE, MOUSE_POLL_DELAY, (TIMERPROC)TrackMouseTimerProc); } - if (_cursor.fix_at) { - int dx = x - _cursor.pos.x; - int dy = y - _cursor.pos.y; - if (dx != 0 || dy != 0) { - _cursor.delta.x = dx; - _cursor.delta.y = dy; - - pt.x = _cursor.pos.x; - pt.y = _cursor.pos.y; - - ClientToScreen(hwnd, &pt); - SetCursorPos(pt.x, pt.y); - } - } else { - _cursor.delta.x = x - _cursor.pos.x; - _cursor.delta.y = y - _cursor.pos.y; - _cursor.pos.x = x; - _cursor.pos.y = y; - _cursor.dirty = true; + if (_cursor.UpdateCursorPosition(x, y, true)) { + pt.x = _cursor.pos.x; + pt.y = _cursor.pos.y; + ClientToScreen(hwnd, &pt); + SetCursorPos(pt.x, pt.y); } MyShowCursor(false); HandleMouseEvents(); Index: src/video/allegro_v.cpp =================================================================== --- src/video/allegro_v.cpp (revision 27164) +++ src/video/allegro_v.cpp (working copy) @@ -388,22 +388,10 @@ } /* Mouse movement */ - int dx = mouse_x - _cursor.pos.x; - int dy = mouse_y - _cursor.pos.y; - if (dx != 0 || dy != 0) { - if (_cursor.fix_at) { - _cursor.delta.x = dx; - _cursor.delta.y = dy; - position_mouse(_cursor.pos.x, _cursor.pos.y); - } else { - _cursor.delta.x = dx; - _cursor.delta.y = dy; - _cursor.pos.x = mouse_x; - _cursor.pos.y = mouse_y; - _cursor.dirty = true; - } - mouse_action = true; + if (_cursor.UpdateCursorPosition(mouse_x, mouse_y, false)) { + position_mouse(_cursor.pos.x, _cursor.pos.y); } + if (_cursor.delta.x != 0 || _cursor.delta.y) mouse_action = true; static int prev_mouse_z = 0; if (prev_mouse_z != mouse_z) { Index: src/video/cocoa/event.mm =================================================================== --- src/video/cocoa/event.mm (revision 27164) +++ src/video/cocoa/event.mm (working copy) @@ -362,22 +362,8 @@ static void QZ_MouseMovedEvent(int x, int y) { - if (_cursor.fix_at) { - int dx = x - _cursor.pos.x; - int dy = y - _cursor.pos.y; - - if (dx != 0 || dy != 0) { - _cursor.delta.x += dx; - _cursor.delta.y += dy; - - QZ_WarpCursor(_cursor.pos.x, _cursor.pos.y); - } - } else { - _cursor.delta.x = x - _cursor.pos.x; - _cursor.delta.y = y - _cursor.pos.y; - _cursor.pos.x = x; - _cursor.pos.y = y; - _cursor.dirty = true; + if (_cursor.UpdateCursorPosition(x, y, false)) { + QZ_WarpCursor(_cursor.pos.x, _cursor.pos.y); } HandleMouseEvents(); } Index: src/gfx.cpp =================================================================== --- src/gfx.cpp (revision 27164) +++ src/gfx.cpp (working copy) @@ -1603,6 +1603,53 @@ SwitchAnimatedCursor(); } +/** + * Update cursor position on mouse movement. + * @param x New X position. + * @param y New Y position. + * @param queued True, if the OS queues mouse warps after pending mouse movement events. + * False, if the warp applies instantaneous. + * @return true, when the OS cursor position should be warped back to this->pos. + */ +bool CursorVars::UpdateCursorPosition(int x, int y, bool queued_warp) +{ + /* Detecting relative mouse movement is somewhat tricky. + * - There may be multiple mouse move events in the video driver queue (esp. when OpenTTD lags a bit). + * - When we request warping the mouse position (return true), a mouse move event is appended at the end of the queue. + * + * So, when this->fix_at is active, we use the following strategy: + * - The first movement triggers the warp to reset the mouse position. + * - Subsequent events have to compute movement relative to the previous event. + * - The relative movement is finished, when we receive the event matching the warp. + */ + + if (x == this->pos.x && y == this->pos.y) { + /* Warp finished. */ + this->queued_warp = false; + } + + this->delta.x = x - (this->queued_warp ? this->last_position.x : this->pos.x); + this->delta.y = y - (this->queued_warp ? this->last_position.y : this->pos.y); + + this->last_position.x = x; + this->last_position.y = y; + + bool need_warp = false; + if (this->fix_at) { + if (!this->queued_warp && (this->delta.x != 0 || this->delta.y != 0)) { + /* Trigger warp. */ + this->queued_warp = queued_warp; + need_warp = true; + } + } else if (this->pos.x != x || this->pos.y != y) { + this->queued_warp = false; // Cancel warping, we are no longer confining the position. + this->dirty = true; + this->pos.x = x; + this->pos.y = y; + } + return need_warp; +} + bool ChangeResInGame(int width, int height) { return (_screen.width == width && _screen.height == height) || VideoDriver::GetInstance()->ChangeResolution(width, height);