GHOST/Wayland: draw a software-cursor when wrapping cursor motion

As Wayland doesn't support moving the cursor, draw a cross-hair cursor
at the location used by Blender.

Without this, the cursor was locked at the location where grab started,
making some actions unusable since the cursor location was invisible.

Resolves T77311.
This commit is contained in:
Campbell Barton 2022-06-08 13:01:31 +10:00
parent b3e0101a35
commit a1d2efd190
Notes: blender-bot 2023-02-14 08:06:35 +01:00
Referenced by issue #98714, Sculpting and hair particle edit broken on Wayland
Referenced by issue #77311, Mouse cursor stays in-place with Ghost/Wayland for operators that grab cursor input
12 changed files with 210 additions and 2 deletions

View File

@ -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.
*/

View File

@ -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.
*/

View File

@ -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.

View File

@ -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,

View File

@ -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();

View File

@ -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;

View File

@ -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;
/**

View File

@ -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);

View File

@ -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);

View File

@ -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)) {

View File

@ -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).

View File

@ -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