diff --git a/intern/ghost/GHOST_C-api.h b/intern/ghost/GHOST_C-api.h index ae749eb3b8c..57714083656 100644 --- a/intern/ghost/GHOST_C-api.h +++ b/intern/ghost/GHOST_C-api.h @@ -402,6 +402,10 @@ extern GHOST_TSuccess GHOST_SetCursorPosition(GHOST_SystemHandle systemhandle, int32_t x, int32_t y); +GHOST_TSuccess GHOST_GetCursorGrabState(GHOST_WindowHandle windowhandle, + GHOST_TAxisFlag *r_wrap_axis, + int r_bounds[4]); + /** * Grabs the cursor for a modal operation, to keep receiving * events when the mouse is outside the window. X11 only, others @@ -896,6 +900,11 @@ extern int setConsoleWindowState(GHOST_TConsoleWindowState action); */ extern int GHOST_UseNativePixels(void); +/** + * Warp the cursor, if supported. + */ +extern int GHOST_SupportsCursorWarp(void); + /** * Focus window after opening, or put them in the background. */ diff --git a/intern/ghost/GHOST_ISystem.h b/intern/ghost/GHOST_ISystem.h index bb91abbadec..8a4412403f7 100644 --- a/intern/ghost/GHOST_ISystem.h +++ b/intern/ghost/GHOST_ISystem.h @@ -304,6 +304,11 @@ class GHOST_ISystem { */ virtual bool useNativePixel(void) = 0; + /** + * Return true when warping the cursor is supported. + */ + virtual bool supportsCursorWarp() = 0; + /** * Focus window after opening, or put them in the background. */ diff --git a/intern/ghost/GHOST_IWindow.h b/intern/ghost/GHOST_IWindow.h index 183e97a4b55..681ad9c5b07 100644 --- a/intern/ghost/GHOST_IWindow.h +++ b/intern/ghost/GHOST_IWindow.h @@ -254,6 +254,10 @@ class GHOST_IWindow { */ virtual GHOST_TSuccess setCursorShape(GHOST_TStandardCursor cursorShape) = 0; + virtual GHOST_TSuccess getCursorGrabBounds(GHOST_Rect &bounds) = 0; + + virtual GHOST_TSuccess getCursorGrabState(GHOST_TAxisFlag &axis_flag, GHOST_Rect &bounds) = 0; + /** * Test if the standard cursor shape is supported by current platform. * \return Indication of success. diff --git a/intern/ghost/GHOST_Types.h b/intern/ghost/GHOST_Types.h index e2ed9830e4e..841c0c33ee1 100644 --- a/intern/ghost/GHOST_Types.h +++ b/intern/ghost/GHOST_Types.h @@ -403,6 +403,8 @@ typedef enum { GHOST_kGrabHide, } GHOST_TGrabCursorMode; +#define GHOST_GRAB_NEEDS_SOFTWARE_CURSOR_FOR_WARP(grab) ((grab) == GHOST_kGrabWrap) + typedef enum { /** Axis that cursor grab will wrap. */ GHOST_kGrabAxisNone = 0, diff --git a/intern/ghost/intern/GHOST_C-api.cpp b/intern/ghost/intern/GHOST_C-api.cpp index 9374d087408..f4c978a88d4 100644 --- a/intern/ghost/intern/GHOST_C-api.cpp +++ b/intern/ghost/intern/GHOST_C-api.cpp @@ -376,6 +376,23 @@ GHOST_TSuccess GHOST_SetCursorGrab(GHOST_WindowHandle windowhandle, mode, wrap_axis, bounds ? &bounds_rect : nullptr, mouse_ungrab_xy ? mouse_xy : nullptr); } +GHOST_TSuccess GHOST_GetCursorGrabState(GHOST_WindowHandle windowhandle, + GHOST_TAxisFlag *r_axis_flag, + int r_bounds[4]) +{ + GHOST_IWindow *window = (GHOST_IWindow *)windowhandle; + GHOST_Rect bounds_rect; + if (!window->getCursorGrabState(*r_axis_flag, bounds_rect)) { + return GHOST_kFailure; + } + + r_bounds[0] = bounds_rect.m_l; + r_bounds[1] = bounds_rect.m_t; + r_bounds[2] = bounds_rect.m_r; + r_bounds[3] = bounds_rect.m_b; + return GHOST_kSuccess; +} + GHOST_TSuccess GHOST_GetModifierKeyState(GHOST_SystemHandle systemhandle, GHOST_TModifierKeyMask mask, int *isDown) @@ -815,6 +832,12 @@ int GHOST_UseNativePixels(void) return system->useNativePixel(); } +int GHOST_SupportsCursorWarp(void) +{ + GHOST_ISystem *system = GHOST_ISystem::getSystem(); + return system->supportsCursorWarp(); +} + void GHOST_UseWindowFocus(int use_focus) { GHOST_ISystem *system = GHOST_ISystem::getSystem(); diff --git a/intern/ghost/intern/GHOST_System.cpp b/intern/ghost/intern/GHOST_System.cpp index 1ddf884bbc5..2aad4f2ea29 100644 --- a/intern/ghost/intern/GHOST_System.cpp +++ b/intern/ghost/intern/GHOST_System.cpp @@ -390,6 +390,11 @@ void GHOST_System::useWindowFocus(const bool use_focus) m_windowFocus = use_focus; } +bool GHOST_System::supportsCursorWarp() +{ + return true; +} + void GHOST_System::initDebug(GHOST_Debug debug) { m_is_debug_enabled = debug.flags & GHOST_kDebugDefault; diff --git a/intern/ghost/intern/GHOST_System.h b/intern/ghost/intern/GHOST_System.h index 4a3cded1fbd..9f2fba1a2c6 100644 --- a/intern/ghost/intern/GHOST_System.h +++ b/intern/ghost/intern/GHOST_System.h @@ -151,10 +151,13 @@ class GHOST_System : public GHOST_ISystem { bool useNativePixel(void); bool m_nativePixel; + bool supportsCursorWarp(void); + /** * Focus window after opening, or put them in the background. */ void useWindowFocus(const bool use_focus); + bool m_windowFocus; /** diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index f6eabb60b05..7e471d5c7d4 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -1920,6 +1920,11 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorVisibility(bool visible) return GHOST_kSuccess; } +bool GHOST_SystemWayland::supportsCursorWarp() +{ + return false; +} + GHOST_TSuccess GHOST_SystemWayland::setCursorGrab(const GHOST_TGrabCursorMode mode, const GHOST_TGrabCursorMode mode_current, @@ -1948,8 +1953,9 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorGrab(const GHOST_TGrabCursorMode mo const bool was_lock = MODE_NEEDS_LOCK(mode_current); const bool use_lock = MODE_NEEDS_LOCK(mode); - const bool was_hide = MODE_NEEDS_HIDE(mode_current); - const bool use_hide = MODE_NEEDS_HIDE(mode); + /* Check for wrap as #supportsCursorWarp isn't supproted. */ + const bool was_hide = MODE_NEEDS_HIDE(mode_current) || (mode_current == GHOST_kGrabWrap); + const bool use_hide = MODE_NEEDS_HIDE(mode) || (mode == GHOST_kGrabWrap); const bool was_confine = MODE_NEEDS_CONFINE(mode_current); const bool use_confine = MODE_NEEDS_CONFINE(mode); diff --git a/intern/ghost/intern/GHOST_SystemWayland.h b/intern/ghost/intern/GHOST_SystemWayland.h index eeb65eb4fc3..5b3e4f8ed75 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.h +++ b/intern/ghost/intern/GHOST_SystemWayland.h @@ -103,6 +103,8 @@ class GHOST_SystemWayland : public GHOST_System { GHOST_TSuccess setCursorVisibility(bool visible); + bool supportsCursorWarp(); + GHOST_TSuccess setCursorGrab(const GHOST_TGrabCursorMode mode, const GHOST_TGrabCursorMode mode_current, wl_surface *surface); diff --git a/intern/ghost/intern/GHOST_Window.cpp b/intern/ghost/intern/GHOST_Window.cpp index 954f0bc244d..12963abf128 100644 --- a/intern/ghost/intern/GHOST_Window.cpp +++ b/intern/ghost/intern/GHOST_Window.cpp @@ -155,10 +155,33 @@ GHOST_TSuccess GHOST_Window::setCursorGrab(GHOST_TGrabCursorMode mode, GHOST_TSuccess GHOST_Window::getCursorGrabBounds(GHOST_Rect &bounds) { + if (m_cursorGrab != GHOST_kGrabWrap) { + return GHOST_kFailure; + } bounds = m_cursorGrabBounds; return (bounds.m_l == -1 && bounds.m_r == -1) ? GHOST_kFailure : GHOST_kSuccess; } +GHOST_TSuccess GHOST_Window::getCursorGrabState(GHOST_TAxisFlag &wrap_axis, GHOST_Rect &bounds) +{ + if (m_cursorGrab == GHOST_kGrabDisable) { + return GHOST_kFailure; + } + + if (m_cursorGrab == GHOST_kGrabWrap) { + bounds = m_cursorGrabBounds; + wrap_axis = m_cursorGrabAxis; + } + else { + bounds.m_l = -1; + bounds.m_r = -1; + bounds.m_t = -1; + bounds.m_b = -1; + wrap_axis = GHOST_kGrabAxisNone; + } + return GHOST_kSuccess; +} + GHOST_TSuccess GHOST_Window::setCursorShape(GHOST_TStandardCursor cursorShape) { if (setWindowCursorShape(cursorShape)) { diff --git a/intern/ghost/intern/GHOST_Window.h b/intern/ghost/intern/GHOST_Window.h index 794e834d5c7..b0042a33a00 100644 --- a/intern/ghost/intern/GHOST_Window.h +++ b/intern/ghost/intern/GHOST_Window.h @@ -152,6 +152,8 @@ class GHOST_Window : public GHOST_IWindow { */ GHOST_TSuccess getCursorGrabBounds(GHOST_Rect &bounds); + GHOST_TSuccess getCursorGrabState(GHOST_TAxisFlag &axis_flag, GHOST_Rect &bounds); + /** * Sets the progress bar value displayed in the window/application icon * \param progress: The progress percentage (0.0 to 1.0). diff --git a/source/blender/windowmanager/intern/wm_draw.c b/source/blender/windowmanager/intern/wm_draw.c index d2ade7b0376..810cc9001fe 100644 --- a/source/blender/windowmanager/intern/wm_draw.c +++ b/source/blender/windowmanager/intern/wm_draw.c @@ -118,6 +118,113 @@ static void wm_paintcursor_draw(bContext *C, ScrArea *area, ARegion *region) /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Draw Software Cursor + * + * Draw the cursor instead of relying on the graphical environment. + * Needed when setting the cursor position (warping) isn't supported (GHOST/WAYLAND). + * \{ */ + +/** + * Track the state of the last drawn cursor. + */ +static struct { + int8_t enabled; + int winid; + int xy[2]; +} g_software_cursor = { + .enabled = -1, + .winid = -1, +}; + +static bool wm_software_cursor_needed(void) +{ + if (UNLIKELY(g_software_cursor.enabled == -1)) { + g_software_cursor.enabled = !GHOST_SupportsCursorWarp(); + } + return g_software_cursor.enabled; +} + +static bool wm_software_cursor_needed_for_window(const wmWindow *win) +{ + BLI_assert(wm_software_cursor_needed()); + return (win->grabcursor == GHOST_kGrabWrap) && GHOST_GetCursorVisibility(win->ghostwin); +} + +static bool wm_software_cursor_motion_test(const wmWindow *win) +{ + return (g_software_cursor.winid != win->winid) || + (g_software_cursor.xy[0] != win->eventstate->xy[0]) || + (g_software_cursor.xy[1] != win->eventstate->xy[1]); +} + +static void wm_software_cursor_motion_update(const wmWindow *win) +{ + + g_software_cursor.winid = win->winid; + g_software_cursor.xy[0] = win->eventstate->xy[0]; + g_software_cursor.xy[1] = win->eventstate->xy[1]; +} + +static void wm_software_cursor_motion_clear(void) +{ + g_software_cursor.winid = -1; + g_software_cursor.xy[0] = -1; + g_software_cursor.xy[1] = -1; +} + +static void wm_software_cursor_draw(wmWindow *win) +{ + int x = win->eventstate->xy[0]; + int y = win->eventstate->xy[1]; + + int bounds[4]; + GHOST_TAxisFlag wrap_axis = 0; + if (GHOST_GetCursorGrabState(win->ghostwin, &wrap_axis, bounds) != GHOST_kFailure) { + if (wrap_axis & GHOST_kAxisX) { + const int min = bounds[0]; + const int max = bounds[2]; + if (min != max) { + x = mod_i(x - min, max - min) + min; + } + } + if (wrap_axis & GHOST_kGrabAxisY) { + const int height = WM_window_pixels_y(win); + const int min = height - bounds[1]; + const int max = height - bounds[3]; + if (min != max) { + y = mod_i(y - max, min - max) + max; + } + } + } + + /* Draw a primitive cross-hair cursor. + * NOTE: the `win->cursor` could be used for drawing although it's complicated as some cursors + * are set by the operating-system, where the pixel information isn't easily available. */ + const float unit = max_ff(U.dpi_fac, 1.0f); + uint pos = GPU_vertformat_attr_add( + immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + + immUniformColor4f(1, 1, 1, 1); + { + const int ofs_line = (8 * unit); + const int ofs_size = (2 * unit); + immRecti(pos, x - ofs_line, y - ofs_size, x + ofs_line, y + ofs_size); + immRecti(pos, x - ofs_size, y - ofs_line, x + ofs_size, y + ofs_line); + } + immUniformColor4f(0, 0, 0, 1); + { + const int ofs_line = (7 * unit); + const int ofs_size = (1 * unit); + immRecti(pos, x - ofs_line, y - ofs_size, x + ofs_line, y + ofs_size); + immRecti(pos, x - ofs_size, y - ofs_line, x + ofs_size, y + ofs_line); + } + immUnbindProgram(); +} + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Post Draw Region on display handlers * \{ */ @@ -862,6 +969,7 @@ static void wm_draw_window_onscreen(bContext *C, wmWindow *win, int view) /* always draw, not only when screen tagged */ if (win->gesture.first) { wm_gesture_draw(win); + wmWindowViewport(win); } /* Needs pixel coords in screen. */ @@ -870,6 +978,16 @@ static void wm_draw_window_onscreen(bContext *C, wmWindow *win, int view) wmWindowViewport(win); } + if (wm_software_cursor_needed()) { + if (wm_software_cursor_needed_for_window(win)) { + wm_software_cursor_draw(win); + wm_software_cursor_motion_update(win); + } + else { + wm_software_cursor_motion_clear(); + } + } + GPU_debug_group_end(); } @@ -1020,6 +1138,12 @@ static bool wm_draw_update_test_window(Main *bmain, bContext *C, wmWindow *win) return true; } + if (wm_software_cursor_needed()) { + if (wm_software_cursor_needed_for_window(win) && wm_software_cursor_motion_test(win)) { + return true; + } + } + #ifndef WITH_XR_OPENXR UNUSED_VARS(wm); #endif