Add SDL Pinch events (#9445)

This commit is contained in:
Sylvain Becker
2025-10-12 23:44:23 +02:00
committed by GitHub
parent e2195621d7
commit 71bf56c9e4
21 changed files with 605 additions and 13 deletions

View File

@@ -121,6 +121,16 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeTouch)(
jint touch_device_id_in, jint pointer_finger_id_in,
jint action, jfloat x, jfloat y, jfloat p);
JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePinchStart)(
JNIEnv *env, jclass jcls);
JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePinchUpdate)(
JNIEnv *env, jclass jcls,
jfloat scale);
JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePinchEnd)(
JNIEnv *env, jclass jcls);
JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeMouse)(
JNIEnv *env, jclass jcls,
jint button, jint action, jfloat x, jfloat y, jboolean relative);
@@ -221,6 +231,9 @@ static JNINativeMethod SDLActivity_tab[] = {
{ "onNativeSoftReturnKey", "()Z", SDL_JAVA_INTERFACE(onNativeSoftReturnKey) },
{ "onNativeKeyboardFocusLost", "()V", SDL_JAVA_INTERFACE(onNativeKeyboardFocusLost) },
{ "onNativeTouch", "(IIIFFF)V", SDL_JAVA_INTERFACE(onNativeTouch) },
{ "onNativePinchStart", "()V", SDL_JAVA_INTERFACE(onNativePinchStart) },
{ "onNativePinchUpdate", "(F)V", SDL_JAVA_INTERFACE(onNativePinchUpdate) },
{ "onNativePinchEnd", "()V", SDL_JAVA_INTERFACE(onNativePinchEnd) },
{ "onNativeMouse", "(IIFFZ)V", SDL_JAVA_INTERFACE(onNativeMouse) },
{ "onNativePen", "(IIIFFF)V", SDL_JAVA_INTERFACE(onNativePen) },
{ "onNativeAccel", "(FFF)V", SDL_JAVA_INTERFACE(onNativeAccel) },
@@ -1366,6 +1379,43 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeTouch)(
SDL_UnlockMutex(Android_ActivityMutex);
}
// Pinch
JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePinchStart)(
JNIEnv *env, jclass jcls)
{
SDL_LockMutex(Android_ActivityMutex);
if (Android_Window) {
SDL_SendPinch(SDL_EVENT_PINCH_BEGIN, 0, Android_Window, 0);
}
SDL_UnlockMutex(Android_ActivityMutex);
}
JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePinchUpdate)(
JNIEnv *env, jclass jcls, jfloat scale)
{
SDL_LockMutex(Android_ActivityMutex);
if (Android_Window) {
SDL_SendPinch(SDL_EVENT_PINCH_UPDATE, 0, Android_Window, scale);
}
SDL_UnlockMutex(Android_ActivityMutex);
}
JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePinchEnd)(
JNIEnv *env, jclass jcls)
{
SDL_LockMutex(Android_ActivityMutex);
if (Android_Window) {
SDL_SendPinch(SDL_EVENT_PINCH_END, 0, Android_Window, 0);
}
SDL_UnlockMutex(Android_ActivityMutex);
}
// Mouse
JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeMouse)(
JNIEnv *env, jclass jcls,

View File

@@ -770,6 +770,20 @@ int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen)
break;
#undef PRINT_FINGER_EVENT
#define PRINT_PINCH_EVENT(event) \
(void)SDL_snprintf(details, sizeof(details), " (timestamp=%u scale=%f)", \
(uint)event->pinch.timestamp, event->pinch.scale)
SDL_EVENT_CASE(SDL_EVENT_PINCH_BEGIN)
PRINT_PINCH_EVENT(event);
break;
SDL_EVENT_CASE(SDL_EVENT_PINCH_UPDATE)
PRINT_PINCH_EVENT(event);
break;
SDL_EVENT_CASE(SDL_EVENT_PINCH_END)
PRINT_PINCH_EVENT(event);
break;
#undef PRINT_PINCH_EVENT
#define PRINT_PTOUCH_EVENT(event) \
(void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u pen_state=%u x=%g y=%g eraser=%s state=%s)", \
(uint)event->ptouch.timestamp, (uint)event->ptouch.windowID, (uint)event->ptouch.which, (uint)event->ptouch.pen_state, event->ptouch.x, event->ptouch.y, \
@@ -902,12 +916,13 @@ static void SDL_LogEvent(const SDL_Event *event)
return;
}
// sensor/mouse/pen/finger motion are spammy, ignore these if they aren't demanded.
// sensor/mouse/pen/finger/pinch motion are spammy, ignore these if they aren't demanded.
if ((SDL_EventLoggingVerbosity < 2) &&
((event->type == SDL_EVENT_MOUSE_MOTION) ||
(event->type == SDL_EVENT_FINGER_MOTION) ||
(event->type == SDL_EVENT_PEN_AXIS) ||
(event->type == SDL_EVENT_PEN_MOTION) ||
(event->type == SDL_EVENT_PINCH_UPDATE) ||
(event->type == SDL_EVENT_GAMEPAD_AXIS_MOTION) ||
(event->type == SDL_EVENT_GAMEPAD_SENSOR_UPDATE) ||
(event->type == SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION) ||

View File

@@ -500,3 +500,19 @@ void SDL_QuitTouch(void)
SDL_free(SDL_touchDevices);
SDL_touchDevices = NULL;
}
int SDL_SendPinch(SDL_EventType type, Uint64 timestamp, SDL_Window *window, float scale)
{
/* Post the event, if desired */
int posted = 0;
if (SDL_EventEnabled(type)) {
SDL_Event event;
event.type = type;
event.common.timestamp = timestamp;
event.pinch.scale = scale;
event.pinch.windowID = window ? SDL_GetWindowID(window) : 0;
posted = (SDL_PushEvent(&event) > 0);
}
return posted;
}

View File

@@ -57,4 +57,7 @@ extern void SDL_DelTouch(SDL_TouchID id);
// Shutdown the touch subsystem
extern void SDL_QuitTouch(void);
// Send Gesture events
extern int SDL_SendPinch(SDL_EventType type, Uint64 timestamp, SDL_Window *window, float scale);
#endif // SDL_touch_c_h_

View File

@@ -1928,6 +1928,16 @@ void SDLTest_PrintEvent(const SDL_Event *event)
event->tfinger.dx, event->tfinger.dy, event->tfinger.pressure);
break;
case SDL_EVENT_PINCH_BEGIN:
SDL_Log("SDL EVENT: Pinch Begin");
break;
case SDL_EVENT_PINCH_UPDATE:
SDL_Log("SDL EVENT: Pinch Update, scale=%f", event->pinch.scale);
break;
case SDL_EVENT_PINCH_END:
SDL_Log("SDL EVENT: Pinch End");
break;
case SDL_EVENT_RENDER_TARGETS_RESET:
SDL_Log("SDL EVENT: render targets reset in window %" SDL_PRIu32, event->render.windowID);
break;
@@ -2238,6 +2248,7 @@ SDL_AppResult SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const
event->type != SDL_EVENT_FINGER_MOTION &&
event->type != SDL_EVENT_PEN_MOTION &&
event->type != SDL_EVENT_PEN_AXIS &&
event->type != SDL_EVENT_PINCH_UPDATE &&
event->type != SDL_EVENT_JOYSTICK_AXIS_MOTION) ||
(state->verbose & VERBOSE_MOTION)) {
SDLTest_PrintEvent(event);

View File

@@ -122,6 +122,7 @@ typedef enum
- (void)touchesMovedWithEvent:(NSEvent *)theEvent;
- (void)touchesEndedWithEvent:(NSEvent *)theEvent;
- (void)touchesCancelledWithEvent:(NSEvent *)theEvent;
- (void)magnifyWithEvent:(NSEvent *) theEvent;
// Touch event handling
- (void)handleTouches:(NSTouchPhase)phase withEvent:(NSEvent *)theEvent;

View File

@@ -1990,6 +1990,27 @@ static void Cocoa_SendMouseButtonClicks(SDL_Mouse *mouse, NSEvent *theEvent, SDL
[self handleTouches:NSTouchPhaseCancelled withEvent:theEvent];
}
- (void)magnifyWithEvent:(NSEvent *)theEvent
{
switch ([theEvent phase]) {
case NSEventPhaseBegan:
SDL_SendPinch(SDL_EVENT_PINCH_BEGIN, Cocoa_GetEventTimestamp([theEvent timestamp]), NULL, 0);
break;
case NSEventPhaseChanged:
{
CGFloat scale = 1.0f + [theEvent magnification];
SDL_SendPinch(SDL_EVENT_PINCH_UPDATE, Cocoa_GetEventTimestamp([theEvent timestamp]), NULL, scale);
}
break;
case NSEventPhaseEnded:
case NSEventPhaseCancelled:
SDL_SendPinch(SDL_EVENT_PINCH_END, Cocoa_GetEventTimestamp([theEvent timestamp]), NULL, 0);
break;
default:
break;
}
}
- (void)handleTouches:(NSTouchPhase)phase withEvent:(NSEvent *)theEvent
{
NSSet *touches = [theEvent touchesMatchingPhase:phase inView:nil];

View File

@@ -46,6 +46,9 @@
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
#if !defined(SDL_PLATFORM_TVOS)
- (IBAction)sdlPinchGesture:(UIPinchGestureRecognizer *)sender;
#endif
- (void)safeAreaInsetsDidChange;

View File

@@ -48,6 +48,7 @@ extern int SDL_AppleTVRemoteOpenedAsJoystick;
SDL_TouchID directTouchId;
SDL_TouchID indirectTouchId;
float pinch_scale;
#if !defined(SDL_PLATFORM_TVOS)
UIPointerInteraction *indirectPointerInteraction API_AVAILABLE(ios(13.4));
@@ -76,6 +77,15 @@ extern int SDL_AppleTVRemoteOpenedAsJoystick;
[self addGestureRecognizer:swipeRight];
#endif
#if !defined(SDL_PLATFORM_TVOS)
/* Pinch gestures */
UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(sdlPinchGesture:)];
pinchGesture.cancelsTouchesInView = NO;
pinchGesture.delaysTouchesBegan = NO;
pinchGesture.delaysTouchesEnded = NO;
[self addGestureRecognizer:pinchGesture];
#endif
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.autoresizesSubviews = YES;
@@ -470,6 +480,39 @@ extern int SDL_AppleTVRemoteOpenedAsJoystick;
(int)SDL_ceilf(self.safeAreaInsets.bottom));
}
#if !defined(SDL_PLATFORM_TVOS)
- (IBAction)sdlPinchGesture:(UIPinchGestureRecognizer *)sender
{
CGFloat scale = sender.scale;
UIGestureRecognizerState state = sender.state;
switch (state) {
case UIGestureRecognizerStateBegan:
pinch_scale = 1.0f;
SDL_SendPinch(SDL_EVENT_PINCH_BEGIN, 0, sdlwindow, 0);
break;
case UIGestureRecognizerStateChanged:
if (pinch_scale > 0.0f) {
SDL_SendPinch(SDL_EVENT_PINCH_UPDATE, 0, sdlwindow, scale / pinch_scale);
}
pinch_scale = scale;
break;
case UIGestureRecognizerStateFailed:
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled:
SDL_SendPinch(SDL_EVENT_PINCH_END, 0, sdlwindow, 0);
break;
default:
break;
}
}
#endif
- (SDL_Scancode)scancodeFromPress:(UIPress *)press
{
if (press.key != nil) {

View File

@@ -45,6 +45,7 @@
#include "tablet-v2-client-protocol.h"
#include "primary-selection-unstable-v1-client-protocol.h"
#include "input-timestamps-unstable-v1-client-protocol.h"
#include "pointer-gestures-unstable-v1-client-protocol.h"
#ifdef HAVE_LIBDECOR_H
#include <libdecor.h>
@@ -1416,6 +1417,43 @@ static const struct wl_touch_listener touch_listener = {
touch_handler_orientation // Version 6
};
void pinch_begin(void *data,
struct zwp_pointer_gesture_pinch_v1 *zwp_pointer_gesture_pinch_v1,
uint32_t serial,
uint32_t time,
struct wl_surface *surface,
uint32_t fingers)
{
SDL_SendPinch(SDL_EVENT_PINCH_BEGIN, 0, NULL, 0);
}
void pinch_update(void *data,
struct zwp_pointer_gesture_pinch_v1 *zwp_pointer_gesture_pinch_v1,
uint32_t time,
wl_fixed_t dx,
wl_fixed_t dy,
wl_fixed_t scale,
wl_fixed_t rotation)
{
float s = (float)(wl_fixed_to_double(scale));
SDL_SendPinch(SDL_EVENT_PINCH_UPDATE, 0, NULL, s);
}
void pinch_end(void *data,
struct zwp_pointer_gesture_pinch_v1 *zwp_pointer_gesture_pinch_v1,
uint32_t serial,
uint32_t time,
int32_t cancelled)
{
SDL_SendPinch(SDL_EVENT_PINCH_END, 0, NULL, 0);
}
static const struct zwp_pointer_gesture_pinch_v1_listener gesture_pinch_listener = {
pinch_begin,
pinch_update,
pinch_end
};
// Fallback for xkb_keymap_key_get_mods_for_level(), which is only available from 1.0.0, while the SDL minimum is 0.5.0.
#if !SDL_XKBCOMMON_CHECK_VERSION(1, 0, 0)
static size_t xkb_legacy_get_mods_for_level(SDL_WaylandSeat *seat, xkb_keycode_t key, xkb_layout_index_t layout, xkb_level_index_t level, xkb_mod_mask_t *masks_out, size_t masks_size)
@@ -2387,6 +2425,10 @@ static void Wayland_SeatDestroyTouch(SDL_WaylandSeat *seat)
}
}
if (seat->touch.gesture_pinch) {
zwp_pointer_gesture_pinch_v1_destroy(seat->touch.gesture_pinch);
}
SDL_zero(seat->touch);
WAYLAND_wl_list_init(&seat->touch.points);
}
@@ -2430,6 +2472,12 @@ static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, enum w
}
SDL_AddTouch((SDL_TouchID)(uintptr_t)seat->touch.wl_touch, SDL_TOUCH_DEVICE_DIRECT, name_fmt);
/* Pinch gesture */
seat->touch.gesture_pinch = zwp_pointer_gestures_v1_get_pinch_gesture(seat->display->zwp_pointer_gestures, seat->pointer.wl_pointer);
zwp_pointer_gesture_pinch_v1_set_user_data(seat->touch.gesture_pinch, seat);
zwp_pointer_gesture_pinch_v1_add_listener(seat->touch.gesture_pinch, &gesture_pinch_listener, seat);
} else if (!(capabilities & WL_SEAT_CAPABILITY_TOUCH) && seat->touch.wl_touch) {
Wayland_SeatDestroyTouch(seat);
}

View File

@@ -192,6 +192,7 @@ typedef struct SDL_WaylandSeat
struct zwp_input_timestamps_v1 *timestamps;
Uint64 highres_timestamp_ns;
struct wl_list points;
struct zwp_pointer_gesture_pinch_v1 *gesture_pinch;
} touch;
struct

View File

@@ -67,6 +67,7 @@
#include "xdg-toplevel-icon-v1-client-protocol.h"
#include "color-management-v1-client-protocol.h"
#include "pointer-warp-v1-client-protocol.h"
#include "pointer-gestures-unstable-v1-client-protocol.h"
#ifdef HAVE_LIBDECOR_H
#include <libdecor.h>
@@ -1322,6 +1323,8 @@ static void handle_registry_global(void *data, struct wl_registry *registry, uin
Wayland_InitColorManager(d);
} else if (SDL_strcmp(interface, "wp_pointer_warp_v1") == 0) {
d->wp_pointer_warp_v1 = wl_registry_bind(d->registry, id, &wp_pointer_warp_v1_interface, 1);
} else if (SDL_strcmp(interface, "zwp_pointer_gestures_v1") == 0) {
d->zwp_pointer_gestures = wl_registry_bind(d->registry, id, &zwp_pointer_gestures_v1_interface, 1);
}
#ifdef SDL_WL_FIXES_VERSION
else if (SDL_strcmp(interface, "wl_fixes") == 0) {
@@ -1645,6 +1648,11 @@ static void Wayland_VideoCleanup(SDL_VideoDevice *_this)
data->wp_pointer_warp_v1 = NULL;
}
if (data->zwp_pointer_gestures) {
zwp_pointer_gestures_v1_destroy(data->zwp_pointer_gestures);
data->zwp_pointer_gestures = NULL;
}
if (data->compositor) {
wl_compositor_destroy(data->compositor);
data->compositor = NULL;

View File

@@ -86,6 +86,7 @@ struct SDL_VideoData
struct wp_color_manager_v1 *wp_color_manager_v1;
struct zwp_tablet_manager_v2 *tablet_manager;
struct wl_fixes *wl_fixes;
struct zwp_pointer_gestures_v1 *zwp_pointer_gestures;
struct xkb_context *xkb_context;

View File

@@ -38,6 +38,11 @@ static bool xinput2_initialized;
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH
static bool xinput2_multitouch_supported;
#endif
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE
static int xinput2_gesture_supported = 0;
#endif
static int X11_Xinput2IsGestureSupported(void);
/* Opcode returned X11_XQueryExtension
* It will be used in event processing
@@ -272,8 +277,8 @@ bool X11_InitXinput2(SDL_VideoDevice *_this)
return false; // X server does not have XInput at all
}
// We need at least 2.2 for Multitouch, 2.0 otherwise.
version = query_xinput2_version(data->display, 2, 2);
// We need at least 2.4 for Gesture, 2.2 for Multitouch, 2.0 otherwise.
version = query_xinput2_version(data->display, 2, 4);
if (!xinput2_version_atleast(version, 2, 0)) {
return false; // X server does not support the version we want at all.
}
@@ -287,6 +292,9 @@ bool X11_InitXinput2(SDL_VideoDevice *_this)
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH // Multitouch needs XInput 2.2
xinput2_multitouch_supported = xinput2_version_atleast(version, 2, 2);
#endif
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE // Gesture needs XInput 2.4
xinput2_gesture_supported = xinput2_version_atleast(version, 2, 4);
#endif
// Populate the atoms for finding relative axes
xinput2_rel_x_atom = X11_XInternAtom(data->display, "Rel X", False);
@@ -735,6 +743,28 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
SDL_SendTouchMotion(0, xev->sourceid, xev->detail, window, x, y, 1.0);
} break;
#endif // SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE
case XI_GesturePinchBegin:
case XI_GesturePinchUpdate:
case XI_GesturePinchEnd:
{
const XIGesturePinchEvent *xev = (const XIGesturePinchEvent *)cookie->data;
float x, y;
SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event);
xinput2_normalize_touch_coordinates(window, xev->event_x, xev->event_y, &x, &y);
if (cookie->evtype == XI_GesturePinchBegin) {
SDL_SendPinch(SDL_EVENT_PINCH_BEGIN, 0, window, 0);
} else if (cookie->evtype == XI_GesturePinchUpdate) {
SDL_SendPinch(SDL_EVENT_PINCH_UPDATE, 0, window, (float)xev->scale);
} else {
SDL_SendPinch(SDL_EVENT_PINCH_END, 0, window, 0);
}
} break;
#endif // SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE
}
#endif // SDL_VIDEO_DRIVER_X11_XINPUT2
}
@@ -774,6 +804,14 @@ void X11_Xinput2Select(SDL_VideoDevice *_this, SDL_Window *window)
XISetMask(mask, XI_Motion);
}
if (X11_Xinput2IsGestureSupported()) {
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE
XISetMask(mask, XI_GesturePinchBegin);
XISetMask(mask, XI_GesturePinchUpdate);
XISetMask(mask, XI_GesturePinchEnd);
#endif
}
X11_XISelectEvents(data->display, window_data->xwindow, &eventmask, 1);
#endif
}
@@ -836,6 +874,15 @@ bool X11_Xinput2SelectMouseAndKeyboard(SDL_VideoDevice *_this, SDL_Window *windo
return false;
}
int X11_Xinput2IsGestureSupported(void)
{
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE
return xinput2_initialized && xinput2_gesture_supported;
#else
return 0;
#endif
}
void X11_Xinput2GrabTouch(SDL_VideoDevice *_this, SDL_Window *window)
{
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH