Confine Mouse Cursor to Window

Hello,

Just upgraded to Cocos2d-x 3.14 and added a toggle between Fullscreen/Windowed. However, on multi-monitor setups the cursor can still leave the Fullscreen window and minimize the application with a left-click.

Is there an easy way to confine the mouse cursor to the window?

Thanks!

@mystical
As far as I know, GLFW doesn’t support system cursor lock(grab, capture, whatever it called).

However, it might not be the best way, they support similar feature. They hide cursor for you, lock it to center and provide unlimited mouse movement with virtual point. Search GLFW_CURSOR_DISABLED for more information.

If window getting minimized is your main problem than locking cursor into window, try to disable GLFW_AUTO_ICONIFY in Fullscreen or make Windowed Fullscreen. Disabling GLFW_AUTO_ICONIFY in Fullscreen will not minimized when window’s focus is lost and Windowed Fullscreen will act like Fullscreen but can be topped by other windows if I’m correct (haven’t tested for awhile).

For current version (3.14), Windowed Fullscreen is not supported, which means you need to implement by yourself (very easy). Also can not be toggled to other window modes, obviously. To do so, you need to upgrade to lastest GLFW from their repo.

Hope this helped.

1 Like

Thank you very much! Just tried that out and it works perfectly, although that’s a bit disappointing you “have” to create a custom cursor. Going to post the solution here for anyone else trying to lock the cursor.

File to modify is libcocos2d->platform->desktop->CCGLViewImpl-desktop.cpp

Add this line to GLViewImpl::createWithFullScreen:glfwSetInputMode(ret->getWindow(), GLFW_CURSOR, GLFW_CURSOR_DISABLED);
Add this line to GLViewImpl::initWithFullscreen:glfwWindowHint(GLFW_AUTO_ICONIFY, GL_FALSE);

Then glview = GLViewImpl::createWithFullScreen(“Your App”); in the AppDelegate.cpp

Done, it was that easy. :smiley: Thanks again bsy6766

@mystical

I was messing up last few days with system cursors and found way to confine system cursor. I only tested with Win32 and Linux, but I believe other platforms support similar feature. I can share you the code here if you are interested and developing on Win32.

1 Like

[quote=“bsy6766, post:4, topic:34377, full:true”]
@mystical

I was messing up last few days with system cursors and found way to confine system cursor. I only tested with Win32 and Linux, but I believe other platforms support similar feature. I can share you the code here if you are interested and developing on Win32.
[/quote]Sure that’d be great! Very interested in how you got it to work.

@mystical

I haven’t tested on other Windows, but should work I guess.

In libcocos2d/platform/desktop/CCGLViewImpl-desktop.h, add

bool _cursorLocked;

I added in protected field after _mouseY variable.

Also add

/**
* Lock or Unlock the system cursor
*/
void setCursorLock(const bool isLocked);

in public field. I added after setCursorVisible(bool) function.


In libcocos2d/platform/desktop/CCGLViewImp-desktop.cpp

Initialize _cursorLocked in constructor

GLViewImpl::GLViewImpl(bool initglfw)
: _captured(false)
, _supportTouch(false)
, _isInRetinaMonitor(false)
, _isRetinaEnabled(false)
, _retinaFactor(1)
, _frameZoomFactor(1.0f)
, _mainWindow(nullptr)
, _monitor(nullptr)
, _mouseX(0.0f)
, _mouseY(0.0f)
, _cursorLocked(false)                 <- here
{
      //....
}

Then add setCursorLock(bool) function.

void GLViewImpl::setCursorLock(bool isLocked)
{
	// Return if window is null
	if (_mainWindow == NULL)
		return;

	_cursorLocked = isLocked;
	if (isLocked)
	{
		// Client rect area. This excludes border, caption, etc. Only gets the inner window area.
		RECT clientRect;
		GetClientRect(glfwGetWin32Window(_mainWindow), &clientRect);

		// Rect to points.
		POINT leftTop, rightBottomn;
		leftTop.x = clientRect.left;
		leftTop.y = clientRect.top;
		rightBottomn.x = clientRect.right;
		rightBottomn.y = clientRect.bottom;

		// Since clientRect starts at (0, 0), we need to convert points to screen position.
		HWND hwnd = glfwGetWin32Window(_mainWindow);
		ClientToScreen(hwnd, &leftTop);
		ClientToScreen(hwnd, &rightBottomn);

		/*
		// Note: Uncomment this block to include title bar to clien rect. This will let user move around window while Windowed mode.
		if (!isFullscreen())
		{
			// Get caption height and apply to point
			int captionHeight = (GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CYCAPTION) +
				GetSystemMetrics(SM_CXPADDEDBORDER));
			leftTop.y -= captionHeight;
		}
		*/

		// Set client Rect to screen point.
		clientRect.left = leftTop.x;
		clientRect.top = leftTop.y;
		clientRect.right = rightBottomn.x;
		clientRect.bottom = rightBottomn.y;

		// Lock cursor in this area
		ClipCursor(&clientRect);
	}
	else
	{
		// Disable lock
		ClipCursor(NULL);
	}
}

I found that ClipCursor disappears when window is moved, resized, etc. So we need to reset every time when that happens

Add

if (_cursorLocked)
{
	// Reset cursor lock when window move. 
	setCursorLock(true);
}

this to

void GLViewImpl::onGLFWWindowPosCallback(GLFWwindow* /*window*/, int /*x*/, int /*y*/)
{
    Director::getInstance()->setViewport();

	if (_cursorLocked)
	{
		// Reset cursor lock when window move. 
		setCursorLock(true);
	}
}

and

void GLViewImpl::onGLFWframebuffersize(GLFWwindow* window, int w, int h)
{
        //.....

	if (_cursorLocked)
	{
		// Reset cursor lock when frame buffer size changes
		setCursorLock(true);
	}
}

and

void GLViewImpl::onGLFWWindowSizeFunCallback(GLFWwindow* /*window*/, int width, int height)
{
    if (width && height && _resolutionPolicy != ResolutionPolicy::UNKNOWN)
    {
            //....
	    if (_cursorLocked)
	    {
	    	  // Reset cursor lock when window size changes
	          setCursorLock(true);
	    }
    }
}

and

void GLViewImpl::onGLFWWindowFocusCallback(GLFWwindow* /*window*/, int focused)
{
    if (focused == GL_TRUE)
    {
        Director::getInstance()->getEventDispatcher()->dispatchCustomEvent(GLViewImpl::EVENT_WINDOW_FOCUSED, nullptr);

	// Reset cursor lock when window gets focused back.
	if (_cursorLocked)
	{
		setCursorLock(true);
	}
    }
    else
    {
        Director::getInstance()->getEventDispatcher()->dispatchCustomEvent(GLViewImpl::EVENT_WINDOW_UNFOCUSED, nullptr);

	// Don't need to unlock cursor when focus is lost. Lock automatically released.
    }
}

If you want the code to run only on Win32, you can wrap above codes with

#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)
    //...
#endif

but it’s unnecessary if cross-platform is not a thing.

I might missed corner cases, but I think this is it.

1 Like

this works on my game, just want to say thanks Bsy6766!