Hit testing tweaks for X11 and Wayland (#8582)

Hit testing on X11 and Wayland should now behave more like it
does on Windows - the current active zone is tracked on mouse
motion events and the cursor is changed accordingly when hovering
a "special" zone (such as the resize handles).
main
Ionuț Leonte 2023-11-20 23:33:11 +02:00 committed by GitHub
parent 5b1c68c2f3
commit b76f8de298
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 134 additions and 36 deletions

View File

@ -32,6 +32,7 @@
#include "SDL_waylandvideo.h"
#include "SDL_waylandevents_c.h"
#include "SDL_waylandwindow.h"
#include "SDL_waylandmouse.h"
#include "pointer-constraints-unstable-v1-client-protocol.h"
#include "relative-pointer-unstable-v1-client-protocol.h"
@ -490,13 +491,26 @@ static void pointer_handle_motion(void *data, struct wl_pointer *pointer,
uint32_t time, wl_fixed_t sx_w, wl_fixed_t sy_w)
{
struct SDL_WaylandInput *input = data;
SDL_WindowData *window = input->pointer_focus;
SDL_WindowData *window_data = input->pointer_focus;
SDL_Window *window = window_data ? window_data->sdlwindow : NULL;
input->sx_w = sx_w;
input->sy_w = sy_w;
if (input->pointer_focus) {
float sx = (float)(wl_fixed_to_double(sx_w) * window->pointer_scale_x);
float sy = (float)(wl_fixed_to_double(sy_w) * window->pointer_scale_y);
SDL_SendMouseMotion(Wayland_GetPointerTimestamp(input, time), window->sdlwindow, 0, 0, sx, sy);
float sx = (float)(wl_fixed_to_double(sx_w) * window_data->pointer_scale_x);
float sy = (float)(wl_fixed_to_double(sy_w) * window_data->pointer_scale_y);
SDL_SendMouseMotion(Wayland_GetPointerTimestamp(input, time), window_data->sdlwindow, 0, 0, sx, sy);
}
if (window && window->hit_test) {
const SDL_Point point = { wl_fixed_to_int(sx_w), wl_fixed_to_int(sy_w) };
SDL_HitTestResult rc = window->hit_test(window, &point, window->hit_test_data);
if (rc == window_data->hit_test_result) {
return;
}
Wayland_SetHitTestCursor(rc);
window_data->hit_test_result = rc;
}
}
@ -540,7 +554,7 @@ static void pointer_handle_enter(void *data, struct wl_pointer *pointer,
/* If the cursor was changed while our window didn't have pointer
* focus, we might need to trigger another call to
* wl_pointer_set_cursor() for the new cursor to be displayed. */
SDL_SetCursor(NULL);
Wayland_SetHitTestCursor(window->hit_test_result);
}
}
@ -580,9 +594,6 @@ static SDL_bool ProcessHitTest(SDL_WindowData *window_data,
SDL_Window *window = window_data->sdlwindow;
if (window->hit_test) {
const SDL_Point point = { wl_fixed_to_int(sx_w), wl_fixed_to_int(sy_w) };
const SDL_HitTestResult rc = window->hit_test(window, &point, window->hit_test_data);
static const uint32_t directions[] = {
XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT, XDG_TOPLEVEL_RESIZE_EDGE_TOP,
XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT, XDG_TOPLEVEL_RESIZE_EDGE_RIGHT,
@ -599,12 +610,14 @@ static SDL_bool ProcessHitTest(SDL_WindowData *window_data,
};
#endif
switch (rc) {
switch (window_data->hit_test_result) {
case SDL_HITTEST_DRAGGABLE:
#ifdef HAVE_LIBDECOR_H
if (window_data->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) {
if (window_data->shell_surface.libdecor.frame) {
libdecor_frame_move(window_data->shell_surface.libdecor.frame, seat, serial);
libdecor_frame_move(window_data->shell_surface.libdecor.frame,
seat,
serial);
}
} else
#endif
@ -628,7 +641,10 @@ static SDL_bool ProcessHitTest(SDL_WindowData *window_data,
#ifdef HAVE_LIBDECOR_H
if (window_data->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) {
if (window_data->shell_surface.libdecor.frame) {
libdecor_frame_resize(window_data->shell_surface.libdecor.frame, seat, serial, directions_libdecor[rc - SDL_HITTEST_RESIZE_TOPLEFT]);
libdecor_frame_resize(window_data->shell_surface.libdecor.frame,
seat,
serial,
directions_libdecor[window_data->hit_test_result - SDL_HITTEST_RESIZE_TOPLEFT]);
}
} else
#endif
@ -637,7 +653,7 @@ static SDL_bool ProcessHitTest(SDL_WindowData *window_data,
xdg_toplevel_resize(window_data->shell_surface.xdg.roleobj.toplevel,
seat,
serial,
directions[rc - SDL_HITTEST_RESIZE_TOPLEFT]);
directions[window_data->hit_test_result - SDL_HITTEST_RESIZE_TOPLEFT]);
}
}
return SDL_TRUE;

View File

@ -41,6 +41,8 @@
#include "../../SDL_hints_c.h"
static SDL_Cursor *sys_cursors[SDL_HITTEST_RESIZE_LEFT + 1];
static int Wayland_SetRelativeMouseMode(SDL_bool enabled);
typedef struct
@ -315,43 +317,44 @@ static SDL_bool wayland_get_system_cursor(SDL_VideoData *vdata, Wayland_CursorDa
vdata->cursor_themes[vdata->num_cursor_themes++].theme = theme;
}
/* Next, find the cursor from the theme... */
/* Next, find the cursor from the theme. Names taken from: */
/* https://www.freedesktop.org/wiki/Specifications/cursor-spec/ */
switch (cdata->system_cursor) {
case SDL_SYSTEM_CURSOR_ARROW:
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "left_ptr");
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "default");
break;
case SDL_SYSTEM_CURSOR_IBEAM:
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "xterm");
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "text");
break;
case SDL_SYSTEM_CURSOR_WAIT:
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "watch");
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "wait");
break;
case SDL_SYSTEM_CURSOR_CROSSHAIR:
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "tcross");
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "crosshair");
break;
case SDL_SYSTEM_CURSOR_WAITARROW:
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "watch");
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "progress");
break;
case SDL_SYSTEM_CURSOR_SIZENWSE:
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "top_left_corner");
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "nw-resize");
break;
case SDL_SYSTEM_CURSOR_SIZENESW:
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "top_right_corner");
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "ne-resize");
break;
case SDL_SYSTEM_CURSOR_SIZEWE:
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "sb_h_double_arrow");
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "e-resize");
break;
case SDL_SYSTEM_CURSOR_SIZENS:
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "sb_v_double_arrow");
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "n-resize");
break;
case SDL_SYSTEM_CURSOR_SIZEALL:
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "fleur");
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "fleur"); // ?
break;
case SDL_SYSTEM_CURSOR_NO:
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "pirate");
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "not-allowed");
break;
case SDL_SYSTEM_CURSOR_HAND:
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "hand2");
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "pointer");
break;
default:
SDL_assert(0);
@ -771,6 +774,23 @@ void Wayland_InitMouse(void)
input->relative_mode_override = SDL_FALSE;
input->cursor_visible = SDL_TRUE;
SDL_HitTestResult r = SDL_HITTEST_NORMAL;
while (r <= SDL_HITTEST_RESIZE_LEFT) {
switch (r) {
case SDL_HITTEST_NORMAL: sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); break;
case SDL_HITTEST_DRAGGABLE: sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); break;
case SDL_HITTEST_RESIZE_TOPLEFT: sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE); break;
case SDL_HITTEST_RESIZE_TOP: sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS); break;
case SDL_HITTEST_RESIZE_TOPRIGHT: sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW); break;
case SDL_HITTEST_RESIZE_RIGHT: sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE); break;
case SDL_HITTEST_RESIZE_BOTTOMRIGHT: sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE); break;
case SDL_HITTEST_RESIZE_BOTTOM: sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS); break;
case SDL_HITTEST_RESIZE_BOTTOMLEFT: sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW); break;
case SDL_HITTEST_RESIZE_LEFT: sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE); break;
}
r++;
}
#ifdef SDL_USE_LIBDBUS
Wayland_DBusInitCursorProperties(d);
#endif
@ -795,4 +815,13 @@ void Wayland_FiniMouse(SDL_VideoData *data)
Wayland_EmulateMouseWarpChanged, input);
}
void Wayland_SetHitTestCursor(SDL_HitTestResult rc)
{
if (rc == SDL_HITTEST_NORMAL || rc == SDL_HITTEST_DRAGGABLE) {
SDL_SetCursor(NULL);
} else {
Wayland_ShowCursor(sys_cursors[rc]);
}
}
#endif /* SDL_VIDEO_DRIVER_WAYLAND */

View File

@ -26,6 +26,7 @@
extern void Wayland_InitMouse(void);
extern void Wayland_FiniMouse(SDL_VideoData *data);
extern void Wayland_SetHitTestCursor(SDL_HitTestResult rc);
#if 0 /* TODO RECONNECT: See waylandvideo.c for more information! */
extern void Wayland_RecreateCursors(void);
#endif /* 0 */

View File

@ -2109,6 +2109,8 @@ int Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Propert
SDL_SetProperty(props, "SDL.window.wayland.surface", data->surface);
SDL_SetProperty(props, "SDL.window.wayland.egl_window", data->egl_window);
data->hit_test_result = SDL_HITTEST_NORMAL;
return 0;
}

View File

@ -121,6 +121,8 @@ struct SDL_WindowData
SDL_bool is_fullscreen;
SDL_bool in_fullscreen_transition;
SDL_bool fullscreen_was_positioned;
SDL_HitTestResult hit_test_result;
};
extern void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window);

View File

@ -559,13 +559,26 @@ static void InitiateWindowResize(SDL_VideoDevice *_this, const SDL_WindowData *d
X11_XSync(display, 0);
}
SDL_bool X11_ProcessHitTest(SDL_VideoDevice *_this, const SDL_WindowData *data, const float x, const float y)
SDL_bool X11_ProcessHitTest(SDL_VideoDevice *_this, SDL_WindowData *data, const float x, const float y, SDL_bool force_new_result)
{
SDL_Window *window = data->window;
if (!window->hit_test) return SDL_FALSE;
const SDL_Point point = { x, y };
SDL_HitTestResult rc = window->hit_test(window, &point, window->hit_test_data);
if (!force_new_result && rc == data->hit_test_result) {
return SDL_TRUE;
}
X11_SetHitTestCursor(rc);
data->hit_test_result = rc;
return SDL_TRUE;
}
SDL_bool X11_TriggerHitTestAction(SDL_VideoDevice *_this, const SDL_WindowData *data, const float x, const float y)
{
SDL_Window *window = data->window;
if (window->hit_test) {
const SDL_Point point = { x, y };
const SDL_HitTestResult rc = window->hit_test(window, &point, window->hit_test_data);
static const int directions[] = {
_NET_WM_MOVERESIZE_SIZE_TOPLEFT, _NET_WM_MOVERESIZE_SIZE_TOP,
_NET_WM_MOVERESIZE_SIZE_TOPRIGHT, _NET_WM_MOVERESIZE_SIZE_RIGHT,
@ -573,7 +586,7 @@ SDL_bool X11_ProcessHitTest(SDL_VideoDevice *_this, const SDL_WindowData *data,
_NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT, _NET_WM_MOVERESIZE_SIZE_LEFT
};
switch (rc) {
switch (data->hit_test_result) {
case SDL_HITTEST_DRAGGABLE:
InitiateWindowMove(_this, data, &point);
return SDL_TRUE;
@ -586,7 +599,7 @@ SDL_bool X11_ProcessHitTest(SDL_VideoDevice *_this, const SDL_WindowData *data,
case SDL_HITTEST_RESIZE_BOTTOM:
case SDL_HITTEST_RESIZE_BOTTOMLEFT:
case SDL_HITTEST_RESIZE_LEFT:
InitiateWindowResize(_this, data, &point, directions[rc - SDL_HITTEST_RESIZE_TOPLEFT]);
InitiateWindowResize(_this, data, &point, directions[data->hit_test_result - SDL_HITTEST_RESIZE_TOPLEFT]);
return SDL_TRUE;
default:
@ -819,7 +832,7 @@ void X11_HandleButtonPress(SDL_VideoDevice *_this, SDL_WindowData *windowdata, i
} else {
SDL_bool ignore_click = SDL_FALSE;
if (button == Button1) {
if (X11_ProcessHitTest(_this, windowdata, x, y)) {
if (X11_TriggerHitTestAction(_this, windowdata, x, y)) {
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_HIT_TEST, 0, 0);
return; /* don't pass this event on to app. */
}
@ -1078,6 +1091,8 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
/* We ungrab in LeaveNotify, so we may need to grab again here */
SDL_UpdateWindowGrab(data->window);
X11_ProcessHitTest(_this, data, mouse->last_x, mouse->last_y, SDL_TRUE);
} break;
/* Losing mouse coverage? */
case LeaveNotify:
@ -1483,6 +1498,7 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
printf("window %p: X11 motion: %d,%d\n", data, xevent->xmotion.x, xevent->xmotion.y);
#endif
X11_ProcessHitTest(_this, data, (float)xevent->xmotion.x, (float)xevent->xmotion.y, SDL_FALSE);
SDL_SendMouseMotion(0, data->window, 0, 0, (float)xevent->xmotion.x, (float)xevent->xmotion.y);
}
} break;

View File

@ -32,6 +32,7 @@ extern void X11_GetBorderValues(SDL_WindowData *data);
extern void X11_HandleButtonPress(SDL_VideoDevice *_this, SDL_WindowData *wdata, int button, const float x, const float y, const unsigned long time);
extern void X11_HandleButtonRelease(SDL_VideoDevice *_this, SDL_WindowData *wdata, int button);
extern SDL_WindowData *X11_FindWindow(SDL_VideoDevice *_this, Window window);
extern SDL_bool X11_ProcessHitTest(SDL_VideoDevice *_this, const SDL_WindowData *data, const float x, const float y);
extern SDL_bool X11_ProcessHitTest(SDL_VideoDevice *_this, SDL_WindowData *data, const float x, const float y, SDL_bool force_new_result);
extern SDL_bool X11_TriggerHitTestAction(SDL_VideoDevice *_this, const SDL_WindowData *data, const float x, const float y);
#endif /* SDL_x11events_h_ */

View File

@ -31,6 +31,8 @@
/* FIXME: Find a better place to put this... */
static Cursor x11_empty_cursor = None;
static SDL_Cursor *sys_cursors[SDL_HITTEST_RESIZE_LEFT + 1];
static Display *GetDisplay(void)
{
return SDL_GetVideoDevice()->driverdata->display;
@ -472,6 +474,23 @@ void X11_InitMouse(SDL_VideoDevice *_this)
mouse->CaptureMouse = X11_CaptureMouse;
mouse->GetGlobalMouseState = X11_GetGlobalMouseState;
SDL_HitTestResult r = SDL_HITTEST_NORMAL;
while (r <= SDL_HITTEST_RESIZE_LEFT) {
switch (r) {
case SDL_HITTEST_NORMAL: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); break;
case SDL_HITTEST_DRAGGABLE: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); break;
case SDL_HITTEST_RESIZE_TOPLEFT: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE); break;
case SDL_HITTEST_RESIZE_TOP: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS); break;
case SDL_HITTEST_RESIZE_TOPRIGHT: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW); break;
case SDL_HITTEST_RESIZE_RIGHT: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE); break;
case SDL_HITTEST_RESIZE_BOTTOMRIGHT: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE); break;
case SDL_HITTEST_RESIZE_BOTTOM: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS); break;
case SDL_HITTEST_RESIZE_BOTTOMLEFT: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW); break;
case SDL_HITTEST_RESIZE_LEFT: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE); break;
}
r++;
}
SDL_SetDefaultCursor(X11_CreateDefaultCursor());
}
@ -490,4 +509,13 @@ void X11_QuitMouse(SDL_VideoDevice *_this)
X11_DestroyEmptyCursor();
}
void X11_SetHitTestCursor(SDL_HitTestResult rc)
{
if (rc == SDL_HITTEST_NORMAL || rc == SDL_HITTEST_DRAGGABLE) {
SDL_SetCursor(NULL);
} else {
X11_ShowCursor(sys_cursors[rc]);
}
}
#endif /* SDL_VIDEO_DRIVER_X11 */

View File

@ -35,5 +35,6 @@ typedef struct SDL_XInput2DeviceInfo
extern void X11_InitMouse(SDL_VideoDevice *_this);
extern void X11_QuitMouse(SDL_VideoDevice *_this);
extern void X11_SetHitTestCursor(SDL_HitTestResult rc);
#endif /* SDL_x11mouse_h_ */

View File

@ -317,6 +317,7 @@ static int SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, Window w)
}
data->window = window;
data->xwindow = w;
data->hit_test_result = SDL_HITTEST_NORMAL;
#ifdef X_HAVE_UTF8_STRING
if (SDL_X11_HAVE_UTF8 && videodata->im) {

View File

@ -79,6 +79,7 @@ struct SDL_WindowData
PointerBarrier barrier[4];
SDL_Rect barrier_rect;
#endif /* SDL_VIDEO_DRIVER_X11_XFIXES */
SDL_HitTestResult hit_test_result;
};
extern void X11_SetNetWMState(SDL_VideoDevice *_this, Window xwindow, Uint32 flags);

View File

@ -395,11 +395,10 @@ int X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
/* button 1 is the pen tip */
if (pressed && SDL_PenPerformHitTest()) {
/* Check whether we should handle window resize / move events */
const SDL_WindowData *windowdata = X11_FindWindow(_this, xev->event);
if (X11_ProcessHitTest(_this, windowdata, pen->last.x, pen->last.y)) {
SDL_SendWindowEvent(windowdata->window, SDL_EVENT_WINDOW_HIT_TEST, 0, 0);
return 1; /* Don't pass on this event */
SDL_WindowData *windowdata = X11_FindWindow(_this, xev->event);
if (windowdata && X11_TriggerHitTestAction(_this, windowdata, pen->last.x, pen->last.y)) {
SDL_SendWindowEvent(windowdata->window, SDL_EVENT_WINDOW_HIT_TEST, 0, 0);
return 1; /* Don't pass on this event */
}
}
SDL_SendPenTipEvent(0, pen->header.id,
@ -467,6 +466,7 @@ int X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
if (!mouse->relative_mode || mouse->relative_mode_warp) {
SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event);
if (window) {
X11_ProcessHitTest(_this, window->driverdata, (float)xev->event_x, (float)xev->event_y, SDL_FALSE);
SDL_SendMouseMotion(0, window, 0, 0, (float)xev->event_x, (float)xev->event_y);
}
}