From 488008e0a22d10c337ef6e910b57a52cecb6bb46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Camilla=20L=C3=B6wy?= Date: Tue, 3 Dec 2019 17:58:20 +0100 Subject: [PATCH] Add cursor mode GLFW_CURSOR_CAPTURED This adds a cursor mode that provides a visible cursor confined to the content area of the window. Fixes #58 --- README.md | 2 ++ docs/input.dox | 12 ++++++++++ docs/news.dox | 9 ++++++++ include/GLFW/glfw3.h | 3 +++ src/cocoa_window.m | 8 +++++++ src/input.c | 3 ++- src/win32_window.c | 15 ++++++++++-- src/wl_platform.h | 1 + src/wl_window.c | 54 +++++++++++++++++++++++++++++++++++++++++++- src/x11_window.c | 12 +++++++--- tests/cursor.c | 8 ++++++- 11 files changed, 119 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 66af82e5..c84f8cb5 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,8 @@ information on what to include when reporting a bug. - Added `GLFW_POINTING_HAND_CURSOR` alias for `GLFW_HAND_CURSOR` (#427) - Added `GLFW_MOUSE_PASSTHROUGH` window hint for letting mouse input pass through the window (#1236,#1568) + - Added `GLFW_CURSOR_CAPTURED` cursor mode to confine the cursor to the window + content area (#58) - Added `GLFW_PLATFORM_UNAVAILABLE` error for platform detection failures (#1958) - Added `GLFW_FEATURE_UNAVAILABLE` error for platform limitations (#1692) - Added `GLFW_FEATURE_UNIMPLEMENTED` error for incomplete backends (#1692) diff --git a/docs/input.dox b/docs/input.dox index 41508e7b..dfb06a43 100644 --- a/docs/input.dox +++ b/docs/input.dox @@ -312,6 +312,16 @@ glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); This mode puts no limit on the motion of the cursor. +If you wish the cursor to be visible but confined to the content area of the +window, set the cursor mode to `GLFW_CURSOR_CAPTURED`. + +@code +glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_CAPTURED); +@endcode + +The cursor will behave normally inside the content area but will not be able to +leave unless the window loses focus. + To exit out of either of these special modes, restore the `GLFW_CURSOR_NORMAL` cursor mode. @@ -319,6 +329,8 @@ cursor mode. glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); @endcode +If the cursor was disabled, this will move it back to its last visible position. + @anchor GLFW_RAW_MOUSE_MOTION @subsection raw_mouse_motion Raw mouse motion diff --git a/docs/news.dox b/docs/news.dox index 98daa3a2..1ff534b8 100644 --- a/docs/news.dox +++ b/docs/news.dox @@ -58,6 +58,14 @@ requesting a specific rendering backend when using contexts. +@subsubsection captured_cursor_34 Captured cursor mode + +GLFW now supports confining the cursor to the window content area with the @ref +GLFW_CURSOR_CAPTURED cursor mode. + +For more information see @ref cursor_mode. + + @subsubsection features_34_init_allocator Support for custom memory allocator GLFW now supports plugging a custom memory allocator at initialization with @ref @@ -234,6 +242,7 @@ then GLFW will fail to initialize. - @ref GLFW_ANGLE_PLATFORM_TYPE_VULKAN - @ref GLFW_ANGLE_PLATFORM_TYPE_METAL - @ref GLFW_X11_XCB_VULKAN_SURFACE + - @ref GLFW_CURSOR_CAPTURED @section news_archive Release notes for earlier versions diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index 8f481790..8a43134c 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -1134,6 +1134,7 @@ extern "C" { #define GLFW_CURSOR_NORMAL 0x00034001 #define GLFW_CURSOR_HIDDEN 0x00034002 #define GLFW_CURSOR_DISABLED 0x00034003 +#define GLFW_CURSOR_CAPTURED 0x00034004 #define GLFW_ANY_RELEASE_BEHAVIOR 0 #define GLFW_RELEASE_BEHAVIOR_FLUSH 0x00035001 @@ -4566,6 +4567,8 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode); * - `GLFW_CURSOR_DISABLED` hides and grabs the cursor, providing virtual * and unlimited cursor movement. This is useful for implementing for * example 3D camera controls. + * - `GLFW_CURSOR_CAPTURED` makes the cursor visible and confines it to the + * content area of the window. * * If the mode is `GLFW_STICKY_KEYS`, the value must be either `GLFW_TRUE` to * enable sticky keys, or `GLFW_FALSE` to disable it. If sticky keys are diff --git a/src/cocoa_window.m b/src/cocoa_window.m index 5bb1289b..42782280 100644 --- a/src/cocoa_window.m +++ b/src/cocoa_window.m @@ -1619,8 +1619,16 @@ void _glfwSetCursorPosCocoa(_GLFWwindow* window, double x, double y) void _glfwSetCursorModeCocoa(_GLFWwindow* window, int mode) { @autoreleasepool { + + if (mode == GLFW_CURSOR_CAPTURED) + { + _glfwInputError(GLFW_FEATURE_UNIMPLEMENTED, + "Cocoa: Captured cursor mode not yet implemented"); + } + if (_glfwWindowFocusedCocoa(window)) updateCursorMode(window); + } // autoreleasepool } diff --git a/src/input.c b/src/input.c index c2a7a86d..36128e10 100644 --- a/src/input.c +++ b/src/input.c @@ -596,7 +596,8 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* handle, int mode, int value) { if (value != GLFW_CURSOR_NORMAL && value != GLFW_CURSOR_HIDDEN && - value != GLFW_CURSOR_DISABLED) + value != GLFW_CURSOR_DISABLED && + value != GLFW_CURSOR_CAPTURED) { _glfwInputError(GLFW_INVALID_ENUM, "Invalid cursor mode 0x%08X", diff --git a/src/win32_window.c b/src/win32_window.c index 4cf25640..f069c115 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -238,7 +238,8 @@ static void applyAspectRatio(_GLFWwindow* window, int edge, RECT* area) // static void updateCursorImage(_GLFWwindow* window) { - if (window->cursorMode == GLFW_CURSOR_NORMAL) + if (window->cursorMode == GLFW_CURSOR_NORMAL || + window->cursorMode == GLFW_CURSOR_CAPTURED) { if (window->cursor) SetCursor(window->cursor->win32.handle); @@ -586,6 +587,8 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l { if (window->cursorMode == GLFW_CURSOR_DISABLED) disableCursor(window); + else if (window->cursorMode == GLFW_CURSOR_CAPTURED) + captureCursor(window); window->win32.frameAction = GLFW_FALSE; } @@ -604,6 +607,8 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l if (window->cursorMode == GLFW_CURSOR_DISABLED) disableCursor(window); + else if (window->cursorMode == GLFW_CURSOR_CAPTURED) + captureCursor(window); return 0; } @@ -612,6 +617,8 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l { if (window->cursorMode == GLFW_CURSOR_DISABLED) enableCursor(window); + else if (window->cursorMode == GLFW_CURSOR_CAPTURED) + releaseCursor(); if (window->monitor && window->autoIconify) _glfwIconifyWindowWin32(window); @@ -981,6 +988,8 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l // resizing the window or using the window menu if (window->cursorMode == GLFW_CURSOR_DISABLED) enableCursor(window); + else if (window->cursorMode == GLFW_CURSOR_CAPTURED) + releaseCursor(); break; } @@ -995,6 +1004,8 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l // resizing the window or using the menu if (window->cursorMode == GLFW_CURSOR_DISABLED) disableCursor(window); + else if (window->cursorMode == GLFW_CURSOR_CAPTURED) + captureCursor(window); break; } @@ -2166,7 +2177,7 @@ void _glfwSetCursorModeWin32(_GLFWwindow* window, int mode) disableRawMouseMotion(window); } - if (mode == GLFW_CURSOR_DISABLED) + if (mode == GLFW_CURSOR_DISABLED || mode == GLFW_CURSOR_CAPTURED) captureCursor(window); else releaseCursor(); diff --git a/src/wl_platform.h b/src/wl_platform.h index f2bf8f03..238e1ed4 100644 --- a/src/wl_platform.h +++ b/src/wl_platform.h @@ -269,6 +269,7 @@ typedef struct _GLFWwindowWayland struct zwp_relative_pointer_v1* relativePointer; struct zwp_locked_pointer_v1* lockedPointer; + struct zwp_confined_pointer_v1* confinedPointer; struct zwp_idle_inhibitor_v1* idleInhibitor; diff --git a/src/wl_window.c b/src/wl_window.c index b4b49b94..cd39c140 100644 --- a/src/wl_window.c +++ b/src/wl_window.c @@ -1860,6 +1860,9 @@ void _glfwDestroyWindowWayland(_GLFWwindow* window) if (window->wl.lockedPointer) zwp_locked_pointer_v1_destroy(window->wl.lockedPointer); + if (window->wl.confinedPointer) + zwp_confined_pointer_v1_destroy(window->wl.confinedPointer); + if (window->context.destroy) window->context.destroy(window); @@ -2538,6 +2541,43 @@ static void lockPointer(_GLFWwindow* window) window); } +static void confinedPointerHandleConfined(void* userData, + struct zwp_confined_pointer_v1* confinedPointer) +{ +} + +static void confinedPointerHandleUnconfined(void* userData, + struct zwp_confined_pointer_v1* confinedPointer) +{ +} + +static const struct zwp_confined_pointer_v1_listener confinedPointerListener = +{ + confinedPointerHandleConfined, + confinedPointerHandleUnconfined +}; + +static void confinePointer(_GLFWwindow* window) +{ + window->wl.confinedPointer = + zwp_pointer_constraints_v1_confine_pointer( + _glfw.wl.pointerConstraints, + window->wl.surface, + _glfw.wl.pointer, + NULL, + ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); + + zwp_confined_pointer_v1_add_listener(window->wl.confinedPointer, + &confinedPointerListener, + window); +} + +static void unconfinePointer(_GLFWwindow* window) +{ + zwp_confined_pointer_v1_destroy(window->wl.confinedPointer); + window->wl.confinedPointer = NULL; +} + void _glfwSetCursorWayland(_GLFWwindow* window, _GLFWcursor* cursor) { if (!_glfw.wl.pointer) @@ -2553,17 +2593,29 @@ void _glfwSetCursorWayland(_GLFWwindow* window, _GLFWcursor* cursor) // Update pointer lock to match cursor mode if (window->cursorMode == GLFW_CURSOR_DISABLED) { + if (window->wl.confinedPointer) + unconfinePointer(window); if (!window->wl.lockedPointer) lockPointer(window); } + else if (window->cursorMode == GLFW_CURSOR_CAPTURED) + { + if (window->wl.lockedPointer) + unlockPointer(window); + if (!window->wl.confinedPointer) + confinePointer(window); + } else if (window->cursorMode == GLFW_CURSOR_NORMAL || window->cursorMode == GLFW_CURSOR_HIDDEN) { if (window->wl.lockedPointer) unlockPointer(window); + else if (window->wl.confinedPointer) + unconfinePointer(window); } - if (window->cursorMode == GLFW_CURSOR_NORMAL) + if (window->cursorMode == GLFW_CURSOR_NORMAL || + window->cursorMode == GLFW_CURSOR_CAPTURED) { if (cursor) setCursorImage(window, &cursor->wl); diff --git a/src/x11_window.c b/src/x11_window.c index f323db6c..4f605771 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -453,7 +453,8 @@ static char* convertLatin1toUTF8(const char* source) // static void updateCursorImage(_GLFWwindow* window) { - if (window->cursorMode == GLFW_CURSOR_NORMAL) + if (window->cursorMode == GLFW_CURSOR_NORMAL || + window->cursorMode == GLFW_CURSOR_CAPTURED) { if (window->cursor) { @@ -1705,6 +1706,8 @@ static void processEvent(XEvent *event) if (window->cursorMode == GLFW_CURSOR_DISABLED) disableCursor(window); + else if (window->cursorMode == GLFW_CURSOR_CAPTURED) + captureCursor(window); if (window->x11.ic) XSetICFocus(window->x11.ic); @@ -1725,6 +1728,8 @@ static void processEvent(XEvent *event) if (window->cursorMode == GLFW_CURSOR_DISABLED) enableCursor(window); + else if (window->cursorMode == GLFW_CURSOR_CAPTURED) + releaseCursor(); if (window->x11.ic) XUnsetICFocus(window->x11.ic); @@ -2831,7 +2836,7 @@ void _glfwSetCursorModeX11(_GLFWwindow* window, int mode) disableRawMouseMotion(window); } - if (mode == GLFW_CURSOR_DISABLED) + if (mode == GLFW_CURSOR_DISABLED || mode == GLFW_CURSOR_CAPTURED) captureCursor(window); else releaseCursor(); @@ -3003,7 +3008,8 @@ void _glfwDestroyCursorX11(_GLFWcursor* cursor) void _glfwSetCursorX11(_GLFWwindow* window, _GLFWcursor* cursor) { - if (window->cursorMode == GLFW_CURSOR_NORMAL) + if (window->cursorMode == GLFW_CURSOR_NORMAL || + window->cursorMode == GLFW_CURSOR_CAPTURED) { updateCursorImage(window); XFlush(_glfw.x11.display); diff --git a/tests/cursor.c b/tests/cursor.c index 9be42748..37f3299c 100644 --- a/tests/cursor.c +++ b/tests/cursor.c @@ -172,7 +172,8 @@ static void key_callback(GLFWwindow* window, int key, int scancode, int action, case GLFW_KEY_ESCAPE: { - if (glfwGetInputMode(window, GLFW_CURSOR) != GLFW_CURSOR_DISABLED) + const int mode = glfwGetInputMode(window, GLFW_CURSOR); + if (mode != GLFW_CURSOR_DISABLED && mode != GLFW_CURSOR_CAPTURED) { glfwSetWindowShouldClose(window, GLFW_TRUE); break; @@ -197,6 +198,11 @@ static void key_callback(GLFWwindow* window, int key, int scancode, int action, printf("(( cursor is hidden ))\n"); break; + case GLFW_KEY_C: + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_CAPTURED); + printf("(( cursor is captured ))\n"); + break; + case GLFW_KEY_R: if (!glfwRawMouseMotionSupported()) break;