View.dispatchTouchEvent(): implement ACTION_CANCEL for sythetic events

This commit is contained in:
Julian Winkler 2025-04-19 15:32:27 +02:00
parent bd83e211d0
commit ec45faaf87
2 changed files with 34 additions and 25 deletions

View file

@ -41,38 +41,53 @@ struct pointer {
GPtrArray *pointer_indices = NULL; GPtrArray *pointer_indices = NULL;
static GdkEvent *canceled_event = NULL; gpointer canceled_event = NULL;
static WrapperWidget *cancel_triggerer = NULL; WrapperWidget *cancel_triggerer = NULL;
int canceled_action = 0;
static struct pointer pointers[MAX_POINTERS] = {}; static struct pointer pointers[MAX_POINTERS] = {};
bool view_dispatch_motionevent(JNIEnv *env, WrapperWidget *wrapper, GtkPropagationPhase phase, jobject motion_event, GdkEvent *event) { bool view_dispatch_motionevent(JNIEnv *env, WrapperWidget *wrapper, GtkPropagationPhase phase, jobject motion_event, gpointer event, int action) {
int ret; int ret;
jobject this = wrapper->jobj; jobject this = wrapper->jobj;
bool is_synthetic = event == motion_event;
if (cancel_triggerer == wrapper) { // cancel done
canceled_event = NULL;
cancel_triggerer = NULL;
if (is_synthetic)
_SET_INT_FIELD(motion_event, "action", canceled_action);
}
if (wrapper->custom_dispatch_touch) { if (wrapper->custom_dispatch_touch) {
ret = (*env)->CallBooleanMethod(env, this, handle_cache.view.dispatchTouchEvent, motion_event); ret = (*env)->CallBooleanMethod(env, this, handle_cache.view.dispatchTouchEvent, motion_event);
} else if (phase == GTK_PHASE_CAPTURE && !wrapper->intercepting_touch) { } else if (phase == GTK_PHASE_CAPTURE && !wrapper->intercepting_touch) {
wrapper->intercepting_touch = (*env)->CallBooleanMethod(env, this, handle_cache.view.onInterceptTouchEvent, motion_event); wrapper->intercepting_touch = (*env)->CallBooleanMethod(env, this, handle_cache.view.onInterceptTouchEvent, motion_event);
if (wrapper->intercepting_touch) { if (wrapper->intercepting_touch) {
if(event) { // store the event that was canceled and let it propagate to the child widgets
// store the event that was canceled and let it propagate to the child widgets canceled_event = event;
canceled_event = event; cancel_triggerer = wrapper;
cancel_triggerer = wrapper; if (is_synthetic) {
} else { canceled_action = _GET_INT_FIELD(motion_event, "action");
/* this function is also called to synthesize an event, in which case there is no GdkEvent so not sure what to do */ _SET_INT_FIELD(motion_event, "action", ACTION_CANCEL);
fprintf(stderr, "view_dispatch_motionevent: onInterceptTouchEvent returned true but this is a synthesized event, please investigate\n");
} }
} }
ret = false; ret = false;
} else { } else {
ret = (*env)->CallBooleanMethod(env, this, handle_cache.view.onTouchEventInternal, motion_event, (jboolean)(event == NULL)); ret = (*env)->CallBooleanMethod(env, this, handle_cache.view.onTouchEventInternal, motion_event, (jboolean)is_synthetic);
} }
if((*env)->ExceptionCheck(env)) if((*env)->ExceptionCheck(env))
(*env)->ExceptionDescribe(env); (*env)->ExceptionDescribe(env);
if (action == ACTION_UP || action == ACTION_CANCEL)
wrapper->intercepting_touch = false;
if (action == ACTION_CANCEL)
ret = false;
return ret; return ret;
} }
@ -93,12 +108,10 @@ static bool call_ontouch_callback(WrapperWidget *wrapper, int action, struct poi
jobject motion_event = (*env)->NewObject(env, handle_cache.motion_event.class, handle_cache.motion_event.constructor, SOURCE_TOUCHSCREEN, action, timestamp, ids, coords); jobject motion_event = (*env)->NewObject(env, handle_cache.motion_event.class, handle_cache.motion_event.constructor, SOURCE_TOUCHSCREEN, action, timestamp, ids, coords);
ret = view_dispatch_motionevent(env, wrapper, phase, motion_event, event); ret = view_dispatch_motionevent(env, wrapper, phase, motion_event, event, action);
(*env)->DeleteLocalRef(env, motion_event); (*env)->DeleteLocalRef(env, motion_event);
if (action == ACTION_UP)
wrapper->intercepting_touch = false;
return ret; return ret;
} }
static void gdk_event_get_widget_relative_position(GdkEvent *event, GtkWidget *widget, double *x, double *y) static void gdk_event_get_widget_relative_position(GdkEvent *event, GtkWidget *widget, double *x, double *y)
@ -201,15 +214,8 @@ static gboolean on_event(GtkEventControllerLegacy *event_controller, GdkEvent *e
pointers[id].raw_x = x; pointers[id].raw_x = x;
pointers[id].raw_y = y; pointers[id].raw_y = y;
// TODO: does this work properly with multitouch? if (event == canceled_event && cancel_triggerer != wrapper) {
if (cancel_triggerer == wrapper) { // cancel done action = ACTION_CANCEL;
canceled_event = NULL;
cancel_triggerer = NULL;
} else if (event == canceled_event) {
gdk_event_get_widget_relative_position(event, widget, &x, &y);
call_ontouch_callback(wrapper, ACTION_CANCEL, pointers, pointer_indices, phase, timestamp, event);
remove_pointer_fast(pointer_indices, &pointers[id]);
return false;
} }
gboolean ret = call_ontouch_callback(wrapper, action, pointers, pointer_indices, phase, timestamp, event); gboolean ret = call_ontouch_callback(wrapper, action, pointers, pointer_indices, phase, timestamp, event);

View file

@ -66,14 +66,17 @@ JNIEXPORT void JNICALL Java_android_view_ViewGroup_native_1drawChild(JNIEnv *env
/* FIXME: put this in a header */ /* FIXME: put this in a header */
G_DECLARE_FINAL_TYPE(JavaWidget, java_widget, JAVA, WIDGET, GtkWidget) G_DECLARE_FINAL_TYPE(JavaWidget, java_widget, JAVA, WIDGET, GtkWidget)
bool view_dispatch_motionevent(JNIEnv *env, WrapperWidget *wrapper, GtkPropagationPhase phase, jobject motion_event, GdkEvent *event); bool view_dispatch_motionevent(JNIEnv *env, WrapperWidget *wrapper, GtkPropagationPhase phase, jobject motion_event, gpointer event, int action);
static bool dispatch_motionevent_if_JavaWidget(GtkWidget *widget, GtkPropagationPhase phase, jobject motion_event) static bool dispatch_motionevent_if_JavaWidget(GtkWidget *widget, GtkPropagationPhase phase, jobject motion_event)
{ {
if(!JAVA_IS_WIDGET(widget)) if(!JAVA_IS_WIDGET(widget))
return false; return false;
JNIEnv *env = get_jni_env();
return view_dispatch_motionevent(get_jni_env(), WRAPPER_WIDGET(gtk_widget_get_parent(widget)), phase, motion_event, NULL); WrapperWidget *wrapper = WRAPPER_WIDGET(gtk_widget_get_parent(widget));
int action = _GET_INT_FIELD(motion_event, "action");
return view_dispatch_motionevent(env, wrapper, phase, motion_event, motion_event, action);
} }
/* used by atl_propagate_synthetic_motionevent */ /* used by atl_propagate_synthetic_motionevent */