From 4d12ad5c903714fa15356e2a33ef46fa30c7f47b Mon Sep 17 00:00:00 2001 From: Mis012 Date: Wed, 26 Mar 2025 20:06:09 +0100 Subject: [PATCH] View: add basic implemntation of dispatchTouchEvent Some Views block all events going to their descendants, and then synthesize new events themselves. Gtk doesn't allow event synthesization, which makes this quite annoying to deal with. This initial implementation definiely has some problems, for example it seems to cause infinite loops in some cases, however it surprisingly works well enough. --- .../android_view_ViewGroup.h | 8 + src/api-impl-jni/util.c | 4 + src/api-impl-jni/util.h | 5 + src/api-impl-jni/views/android_view_View.c | 50 ++- .../views/android_view_ViewGroup.c | 100 ++++++ src/api-impl-jni/views/gdkarrayimpl.c | 326 ++++++++++++++++++ src/api-impl-jni/widgets/WrapperWidget.c | 6 +- src/api-impl/android/view/View.java | 20 +- src/api-impl/android/view/ViewGroup.java | 23 +- 9 files changed, 518 insertions(+), 24 deletions(-) create mode 100644 src/api-impl-jni/views/gdkarrayimpl.c diff --git a/src/api-impl-jni/generated_headers/android_view_ViewGroup.h b/src/api-impl-jni/generated_headers/android_view_ViewGroup.h index c60d66f9..f5ac7150 100644 --- a/src/api-impl-jni/generated_headers/android_view_ViewGroup.h +++ b/src/api-impl-jni/generated_headers/android_view_ViewGroup.h @@ -231,6 +231,14 @@ JNIEXPORT void JNICALL Java_android_view_ViewGroup_native_1drawChildren JNIEXPORT void JNICALL Java_android_view_ViewGroup_native_1drawChild (JNIEnv *, jobject, jlong, jlong, jlong); +/* + * Class: android_view_ViewGroup + * Method: native_dispatchTouchEvent + * Signature: (JLandroid/view/MotionEvent;DD)Z + */ +JNIEXPORT jboolean JNICALL Java_android_view_ViewGroup_native_1dispatchTouchEvent + (JNIEnv *, jobject, jlong, jobject, jdouble, jdouble); + #ifdef __cplusplus } #endif diff --git a/src/api-impl-jni/util.c b/src/api-impl-jni/util.c index 8fa829df..ba1716be 100644 --- a/src/api-impl-jni/util.c +++ b/src/api-impl-jni/util.c @@ -132,6 +132,7 @@ void set_up_handle_cache(JNIEnv *env) handle_cache.view.getScrollY = _METHOD(handle_cache.view.class, "getScrollY", "()I"); handle_cache.view.performClick = _METHOD(handle_cache.view.class, "performClick", "()Z"); handle_cache.view.onTouchEvent = _METHOD(handle_cache.view.class, "onTouchEvent", "(Landroid/view/MotionEvent;)Z"); + handle_cache.view.onTouchEventInternal = _METHOD(handle_cache.view.class, "onTouchEventInternal", "(Landroid/view/MotionEvent;)Z"); handle_cache.view.dispatchTouchEvent = _METHOD(handle_cache.view.class, "dispatchTouchEvent", "(Landroid/view/MotionEvent;)Z"); handle_cache.view.onInterceptTouchEvent = _METHOD(handle_cache.view.class, "onInterceptTouchEvent", "(Landroid/view/MotionEvent;)Z"); handle_cache.view.layoutInternal = _METHOD(handle_cache.view.class, "layoutInternal", "(II)V"); @@ -143,6 +144,9 @@ void set_up_handle_cache(JNIEnv *env) handle_cache.view.dispatchKeyEvent = _METHOD(handle_cache.view.class, "dispatchKeyEvent", "(Landroid/view/KeyEvent;)Z"); handle_cache.view.onKeyDown = _METHOD(handle_cache.view.class, "onKeyDown", "(ILandroid/view/KeyEvent;)Z"); + handle_cache.view_group.class = _REF((*env)->FindClass(env, "android/view/ViewGroup")); + handle_cache.view_group.dispatchTouchEvent = _METHOD(handle_cache.view_group.class, "dispatchTouchEvent", "(Landroid/view/MotionEvent;)Z"); + handle_cache.asset_manager.class = _REF((*env)->FindClass(env, "android/content/res/AssetManager")); handle_cache.asset_manager.extractFromAPK = _STATIC_METHOD(handle_cache.asset_manager.class, "extractFromAPK", "(Ljava/lang/String;Ljava/lang/String;)V"); diff --git a/src/api-impl-jni/util.h b/src/api-impl-jni/util.h index a8f92048..5d343344 100644 --- a/src/api-impl-jni/util.h +++ b/src/api-impl-jni/util.h @@ -77,6 +77,7 @@ struct handle_cache { jmethodID getScrollY; jmethodID performClick; jmethodID onTouchEvent; + jmethodID onTouchEventInternal; jmethodID dispatchTouchEvent; jmethodID onInterceptTouchEvent; jmethodID layoutInternal; @@ -88,6 +89,10 @@ struct handle_cache { jmethodID dispatchKeyEvent; jmethodID onKeyDown; } view; + struct { + jclass class; + jmethodID dispatchTouchEvent; + } view_group; struct { jclass class; jmethodID extractFromAPK; diff --git a/src/api-impl-jni/views/android_view_View.c b/src/api-impl-jni/views/android_view_View.c index f6e4eea3..191167cc 100644 --- a/src/api-impl-jni/views/android_view_View.c +++ b/src/api-impl-jni/views/android_view_View.c @@ -46,11 +46,40 @@ static WrapperWidget *cancel_triggerer = NULL; static struct pointer pointers[MAX_POINTERS] = {}; +bool view_dispatch_motionevent(JNIEnv *env, WrapperWidget *wrapper, GtkPropagationPhase phase, jobject motion_event, GdkEvent *event) { + int ret; + + jobject this = wrapper->jobj; + + if (wrapper->custom_dispatch_touch) { + ret = (*env)->CallBooleanMethod(env, this, handle_cache.view.dispatchTouchEvent, motion_event); + } else if (phase == GTK_PHASE_CAPTURE && !wrapper->intercepting_touch) { + wrapper->intercepting_touch = (*env)->CallBooleanMethod(env, this, handle_cache.view.onInterceptTouchEvent, motion_event); + if (wrapper->intercepting_touch) { + if(event) { + // store the event that was canceled and let it propagate to the child widgets + canceled_event = event; + cancel_triggerer = wrapper; + } else { + /* this function is also called to synthesize an event, in which case there is no GdkEvent so not sure what to do */ + fprintf(stderr, "view_dispatch_motionevent: onInterceptTouchEvent returned true but this is a synthesized event, please investigate\n"); + } + } + ret = false; + } else { + ret = (*env)->CallBooleanMethod(env, this, handle_cache.view.onTouchEventInternal, motion_event); + } + + if((*env)->ExceptionCheck(env)) + (*env)->ExceptionDescribe(env); + + return ret; +} + static bool call_ontouch_callback(WrapperWidget *wrapper, int action, struct pointer pointers[MAX_POINTERS], GPtrArray *pointer_indices, GtkPropagationPhase phase, guint32 timestamp, GdkEvent *event) { bool ret; JNIEnv *env = get_jni_env(); - jobject this = wrapper->jobj; int num_pointers = pointer_indices->len; jintArray ids = (*env)->NewIntArray(env, num_pointers); @@ -62,24 +91,9 @@ static bool call_ontouch_callback(WrapperWidget *wrapper, int action, struct poi (*env)->SetFloatArrayRegion(env, coords, 4 * i, 4, &pointer->coord_x); } - jobject motion_event = (*env)->NewObject(env, handle_cache.motion_event.class, handle_cache.motion_event.constructor, SOURCE_TOUCHSCREEN, action, (long)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); - if (wrapper->custom_dispatch_touch) { - ret = (*env)->CallBooleanMethod(env, this, handle_cache.view.dispatchTouchEvent, motion_event); - } else if (phase == GTK_PHASE_CAPTURE && !wrapper->intercepting_touch) { - wrapper->intercepting_touch = (*env)->CallBooleanMethod(env, this, handle_cache.view.onInterceptTouchEvent, motion_event); - if (wrapper->intercepting_touch) { - // store the event that was canceled and let it propagate to the child widgets - canceled_event = event; - cancel_triggerer = wrapper; - } - ret = false; - } else { - ret = (*env)->CallBooleanMethod(env, this, handle_cache.view.onTouchEvent, motion_event); - } - - if((*env)->ExceptionCheck(env)) - (*env)->ExceptionDescribe(env); + ret = view_dispatch_motionevent(env, wrapper, phase, motion_event, event); (*env)->DeleteLocalRef(env, motion_event); diff --git a/src/api-impl-jni/views/android_view_ViewGroup.c b/src/api-impl-jni/views/android_view_ViewGroup.c index 5cf6996c..d8be5d43 100644 --- a/src/api-impl-jni/views/android_view_ViewGroup.c +++ b/src/api-impl-jni/views/android_view_ViewGroup.c @@ -63,3 +63,103 @@ JNIEXPORT void JNICALL Java_android_view_ViewGroup_native_1drawChild(JNIEnv *env gtk_widget_queue_draw(child); // FIXME: why didn't compose UI invalidate the child? gtk_widget_snapshot_child(widget, child, snapshot); } + +/* FIXME: put this in a header */ +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); + +static bool dispatch_motionevent_if_JavaWidget(GtkWidget *widget, GtkPropagationPhase phase, jobject motion_event) +{ + if(!JAVA_IS_WIDGET(widget)) + return false; + + return view_dispatch_motionevent(get_jni_env(), WRAPPER_WIDGET(gtk_widget_get_parent(widget)), phase, motion_event, NULL); +} + +/* used by atl_propagate_synthetic_motionevent */ +#define GDK_ARRAY_ELEMENT_TYPE GtkWidget * +#define GDK_ARRAY_TYPE_NAME GtkWidgetStack +#define GDK_ARRAY_NAME gtk_widget_stack +#define GDK_ARRAY_FREE_FUNC g_object_unref +#define GDK_ARRAY_PREALLOC 16 +#include "gdkarrayimpl.c" + +/* based on gtk_propagate_event_internal © GTK Team */ +bool atl_propagate_synthetic_motionevent(GtkWidget *widget, jobject motionevent, GtkWidget *toplevel) +{ + int handled_event = false; + GtkWidgetStack widget_array; + int i; + + /* First, propagate event down */ + gtk_widget_stack_init(&widget_array); + gtk_widget_stack_append(&widget_array, g_object_ref(widget)); + + for (;;) { + widget = gtk_widget_get_parent(widget); + if (!widget) + break; + + if (widget == toplevel) + break; + + gtk_widget_stack_append(&widget_array, g_object_ref(widget)); + } + + i = gtk_widget_stack_get_size(&widget_array) - 1; + for (;;) { + widget = gtk_widget_stack_get(&widget_array, i); + + if (!gtk_widget_is_sensitive(widget)) { + handled_event = true; + } else if (gtk_widget_get_realized(widget)) + handled_event = dispatch_motionevent_if_JavaWidget(widget, GTK_PHASE_CAPTURE, motionevent); + + handled_event |= !gtk_widget_get_realized(widget); + + if (handled_event) + break; + + if (i == 0) + break; + + i--; + } + + /* If not yet handled, also propagate back up */ + if (!handled_event) { + /* Propagate event up the widget tree so that + * parents can see the button and motion + * events of the children. + */ + for (i = 0; i < gtk_widget_stack_get_size(&widget_array); i++) { + widget = gtk_widget_stack_get(&widget_array, i); + + /* Scroll events are special cased here because it + * feels wrong when scrolling a GtkViewport, say, + * to have children of the viewport eat the scroll + * event + */ + if (!gtk_widget_is_sensitive(widget)) + handled_event = true; + else if (gtk_widget_get_realized(widget)) + handled_event = dispatch_motionevent_if_JavaWidget(widget, GTK_PHASE_BUBBLE, motionevent); + + handled_event |= !gtk_widget_get_realized(widget); + + if (handled_event) + break; + } + } + + gtk_widget_stack_clear(&widget_array); + return handled_event; +} + +JNIEXPORT jboolean JNICALL Java_android_view_ViewGroup_native_1dispatchTouchEvent(JNIEnv *env, jobject this, jlong widget_ptr, jobject motion_event, jdouble x, jdouble y) +{ + GtkWidget *widget = GTK_WIDGET(_PTR(widget_ptr)); + GtkWidget *picked_child = gtk_widget_pick(widget, x, y, GTK_PICK_DEFAULT); + + return atl_propagate_synthetic_motionevent(picked_child, motion_event, widget); +} diff --git a/src/api-impl-jni/views/gdkarrayimpl.c b/src/api-impl-jni/views/gdkarrayimpl.c new file mode 100644 index 00000000..dfde3ce4 --- /dev/null +++ b/src/api-impl-jni/views/gdkarrayimpl.c @@ -0,0 +1,326 @@ +/* lifted from Gtk for gtk_propagate_event_internal derived function */ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include + +G_BEGIN_DECLS + +#ifndef GDK_ARRAY_TYPE_NAME +#define GDK_ARRAY_TYPE_NAME GdkArray +#endif + +#ifndef GDK_ARRAY_NAME +#define GDK_ARRAY_NAME gdk_array +#endif + +#ifndef GDK_ARRAY_ELEMENT_TYPE +#define GDK_ARRAY_ELEMENT_TYPE gpointer +#endif + +#ifdef GDK_ARRAY_PREALLOC +#if GDK_ARRAY_PREALLOC == 0 +#undef GDK_ARRAY_PREALLOC +#endif +#endif + +#ifdef GDK_ARRAY_NULL_TERMINATED +#define GDK_ARRAY_REAL_SIZE(_size) ((_size) + 1) +#define GDK_ARRAY_MAX_SIZE (G_MAXSIZE / sizeof(_T_) - 1) +#else +#define GDK_ARRAY_REAL_SIZE(_size) (_size) +#define GDK_ARRAY_MAX_SIZE (G_MAXSIZE / sizeof(_T_)) +#endif + +/* make this readable */ +#define _T_ GDK_ARRAY_ELEMENT_TYPE +#define GdkArray GDK_ARRAY_TYPE_NAME +#define gdk_array_paste_more(GDK_ARRAY_NAME, func_name) GDK_ARRAY_NAME##_##func_name +#define gdk_array_paste(GDK_ARRAY_NAME, func_name) gdk_array_paste_more(GDK_ARRAY_NAME, func_name) +#define gdk_array(func_name) gdk_array_paste(GDK_ARRAY_NAME, func_name) + +typedef struct GdkArray GdkArray; + +struct GdkArray { + _T_ *start; + _T_ *end; + _T_ *end_allocation; +#ifdef GDK_ARRAY_PREALLOC + _T_ preallocated[GDK_ARRAY_REAL_SIZE(GDK_ARRAY_PREALLOC)]; +#endif +}; + +/* no G_GNUC_UNUSED here, if you don't use an array type, remove it. */ +static inline void +gdk_array(init)(GdkArray *self) +{ +#ifdef GDK_ARRAY_PREALLOC + self->start = self->preallocated; + self->end = self->start; + self->end_allocation = self->start + GDK_ARRAY_PREALLOC; +#ifdef GDK_ARRAY_NULL_TERMINATED + *self->start = *(_T_[1]){0}; +#endif +#else + self->start = NULL; + self->end = NULL; + self->end_allocation = NULL; +#endif +} + +G_GNUC_UNUSED static inline gsize +gdk_array(get_capacity)(const GdkArray *self) +{ + return self->end_allocation - self->start; +} + +G_GNUC_UNUSED static inline gsize +gdk_array(get_size)(const GdkArray *self) +{ + return self->end - self->start; +} + +static inline void +gdk_array(free_elements)(_T_ *start, + _T_ *end) +{ +#ifdef GDK_ARRAY_FREE_FUNC + _T_ *e; + for (e = start; e < end; e++) +#ifdef GDK_ARRAY_BY_VALUE + GDK_ARRAY_FREE_FUNC(e); +#else + GDK_ARRAY_FREE_FUNC(*e); +#endif +#endif +} + +/* no G_GNUC_UNUSED here */ +static inline void +gdk_array(clear)(GdkArray *self) +{ + gdk_array(free_elements)(self->start, self->end); + +#ifdef GDK_ARRAY_PREALLOC + if (self->start != self->preallocated) +#endif + g_free(self->start); + gdk_array(init)(self); +} + +/* + * gdk_array_steal: + * @self: the array + * + * Steals all data in the array and clears the array. + * + * If you need to know the size of the data, you should query it + * beforehand. + * + * Returns: The array's data + **/ +G_GNUC_UNUSED static inline _T_ * +gdk_array(steal)(GdkArray *self) +{ + _T_ *result; + +#ifdef GDK_ARRAY_PREALLOC + if (self->start == self->preallocated) { + gsize size = GDK_ARRAY_REAL_SIZE(gdk_array(get_size)(self)); + result = g_new(_T_, size); + memcpy(result, self->preallocated, sizeof(_T_) * size); + } else +#endif + result = self->start; + + gdk_array(init)(self); + + return result; +} + +G_GNUC_UNUSED static inline _T_ * +gdk_array(get_data)(const GdkArray *self) +{ + return self->start; +} + +G_GNUC_UNUSED static inline _T_ * +gdk_array(index)(const GdkArray *self, + gsize pos) +{ + return self->start + pos; +} + +G_GNUC_UNUSED static inline gboolean +gdk_array(is_empty)(const GdkArray *self) +{ + return self->end == self->start; +} + +G_GNUC_UNUSED static inline void +gdk_array(reserve)(GdkArray *self, + gsize n) +{ + gsize new_capacity, size, capacity; + + if (G_UNLIKELY(n > GDK_ARRAY_MAX_SIZE)) + g_error("requesting array size of %zu, but maximum size is %zu", n, GDK_ARRAY_MAX_SIZE); + + capacity = gdk_array(get_capacity)(self); + if (n <= capacity) + return; + + size = gdk_array(get_size)(self); + /* capacity * 2 can overflow, that's why we MAX() */ + new_capacity = MAX(GDK_ARRAY_REAL_SIZE(n), capacity * 2); + +#ifdef GDK_ARRAY_PREALLOC + if (self->start == self->preallocated) { + self->start = g_new(_T_, new_capacity); + memcpy(self->start, self->preallocated, sizeof(_T_) * GDK_ARRAY_REAL_SIZE(size)); + } else +#endif +#ifdef GDK_ARRAY_NULL_TERMINATED + if (self->start == NULL) { + self->start = g_new(_T_, new_capacity); + *self->start = *(_T_[1]){0}; + } else +#endif + self->start = g_renew(_T_, self->start, new_capacity); + + self->end = self->start + size; + self->end_allocation = self->start + new_capacity; +#ifdef GDK_ARRAY_NULL_TERMINATED + self->end_allocation--; +#endif +} + +G_GNUC_UNUSED static inline void +gdk_array(splice)(GdkArray *self, + gsize pos, + gsize removed, + gboolean stolen, +#ifdef GDK_ARRAY_BY_VALUE + const _T_ *additions, +#else + _T_ *additions, +#endif + gsize added) +{ + gsize size; + gsize remaining; + + size = gdk_array(get_size)(self); + g_assert(pos + removed <= size); + remaining = size - pos - removed; + + if (!stolen) + gdk_array(free_elements)(gdk_array(index)(self, pos), + gdk_array(index)(self, pos + removed)); + + gdk_array(reserve)(self, size - removed + added); + + if (GDK_ARRAY_REAL_SIZE(remaining) && removed != added) + memmove(gdk_array(index)(self, pos + added), + gdk_array(index)(self, pos + removed), + GDK_ARRAY_REAL_SIZE(remaining) * sizeof(_T_)); + + if (added) { + if (additions) + memcpy(gdk_array(index)(self, pos), + additions, + added * sizeof(_T_)); +#ifndef GDK_ARRAY_NO_MEMSET + else + memset(gdk_array(index)(self, pos), 0, added * sizeof(_T_)); +#endif + } + + /* might overflow, but does the right thing */ + self->end += added - removed; +} + +G_GNUC_UNUSED static void +gdk_array(set_size)(GdkArray *self, + gsize new_size) +{ + gsize old_size = gdk_array(get_size)(self); + if (new_size > old_size) + gdk_array(splice)(self, old_size, 0, FALSE, NULL, new_size - old_size); + else + gdk_array(splice)(self, new_size, old_size - new_size, FALSE, NULL, 0); +} + +G_GNUC_UNUSED static void +gdk_array(append)(GdkArray *self, +#ifdef GDK_ARRAY_BY_VALUE + _T_ *value) +#else + _T_ value) +#endif +{ + gdk_array(splice)(self, + gdk_array(get_size)(self), + 0, + FALSE, +#ifdef GDK_ARRAY_BY_VALUE + value, +#else + &value, +#endif + 1); +} + +#ifdef GDK_ARRAY_BY_VALUE +G_GNUC_UNUSED static _T_ * +gdk_array(get)(const GdkArray *self, + gsize pos) +{ + return gdk_array(index)(self, pos); +} +#else +G_GNUC_UNUSED static _T_ +gdk_array(get)(const GdkArray *self, + gsize pos) +{ + return *gdk_array(index)(self, pos); +} +#endif + +#ifndef GDK_ARRAY_NO_UNDEF + +#undef _T_ +#undef GdkArray +#undef gdk_array_paste_more +#undef gdk_array_paste +#undef gdk_array +#undef GDK_ARRAY_REAL_SIZE +#undef GDK_ARRAY_MAX_SIZE + +#undef GDK_ARRAY_BY_VALUE +#undef GDK_ARRAY_ELEMENT_TYPE +#undef GDK_ARRAY_FREE_FUNC +#undef GDK_ARRAY_NAME +#undef GDK_ARRAY_NULL_TERMINATED +#undef GDK_ARRAY_PREALLOC +#undef GDK_ARRAY_TYPE_NAME +#undef GDK_ARRAY_NO_MEMSET +#endif + +G_END_DECLS diff --git a/src/api-impl-jni/widgets/WrapperWidget.c b/src/api-impl-jni/widgets/WrapperWidget.c index 82858880..cb418cfb 100644 --- a/src/api-impl-jni/widgets/WrapperWidget.c +++ b/src/api-impl-jni/widgets/WrapperWidget.c @@ -23,7 +23,7 @@ static void wrapper_widget_set_property (GObject *object, guint property_id, con } } -static void wrapper_widget_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) +static void wrapper_widget_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { WrapperWidget *self = WRAPPER_WIDGET(object); @@ -91,7 +91,7 @@ static void wrapper_widget_get_property (GObject *object, guint property_id, GVa } } -static void wrapper_widget_init (WrapperWidget *wrapper_widget) +static void wrapper_widget_init(WrapperWidget *wrapper_widget) { } @@ -386,7 +386,7 @@ void wrapper_widget_set_jobject(WrapperWidget *wrapper, JNIEnv *env, jobject job jmethodID ontouchevent_method = _METHOD(_CLASS(jobj), "onTouchEvent", "(Landroid/view/MotionEvent;)Z"); jmethodID dispatchtouchevent_method = _METHOD(_CLASS(jobj), "dispatchTouchEvent", "(Landroid/view/MotionEvent;)Z"); - wrapper->custom_dispatch_touch = dispatchtouchevent_method != handle_cache.view.dispatchTouchEvent; + wrapper->custom_dispatch_touch = (dispatchtouchevent_method != handle_cache.view.dispatchTouchEvent && dispatchtouchevent_method != handle_cache.view_group.dispatchTouchEvent); if (ontouchevent_method != handle_cache.view.onTouchEvent || wrapper->custom_dispatch_touch) { _setOnTouchListener(env, jobj, GTK_WIDGET(wrapper)); } diff --git a/src/api-impl/android/view/View.java b/src/api-impl/android/view/View.java index 6033818a..d502d818 100644 --- a/src/api-impl/android/view/View.java +++ b/src/api-impl/android/view/View.java @@ -1075,13 +1075,22 @@ public class View implements Drawable.Callback { } private OnTouchListener on_touch_listener = null; - public boolean onTouchEvent(MotionEvent event) { + + public boolean onTouchEventInternal(MotionEvent event) { boolean handled = false; if (on_touch_listener != null) handled = on_touch_listener.onTouch(this, event); + + if (!handled) + handled = onTouchEvent(event); + return handled; } + public boolean onTouchEvent(MotionEvent event) { + return false; + } + public void setOnTouchListener(OnTouchListener l) { nativeSetOnTouchListener(widget); on_touch_listener = l; @@ -1206,12 +1215,21 @@ public class View implements Drawable.Callback { public native void setBackgroundColor(int color); public native void native_setVisibility(long widget, int visibility, float alpha); + + protected void onVisibilityChanged(View changedView, int visibility) { + } + + protected void dispatchVisibilityChanged(View changedView, int visibility) { + onVisibilityChanged(changedView, visibility); + } + public void setVisibility(int visibility) { native_setVisibility(widget, visibility, alpha); if ((visibility == View.GONE) != (this.visibility == View.GONE)) { requestLayout(); } this.visibility = visibility; + dispatchVisibilityChanged(this, visibility); } public void setPadding(int left, int top, int right, int bottom) { diff --git a/src/api-impl/android/view/ViewGroup.java b/src/api-impl/android/view/ViewGroup.java index b8defa1d..e9b2b9bb 100644 --- a/src/api-impl/android/view/ViewGroup.java +++ b/src/api-impl/android/view/ViewGroup.java @@ -61,6 +61,10 @@ public class ViewGroup extends View implements ViewParent, ViewManager { addView(child, params); } + public void addView(View child, int index, LayoutParams params) { + addViewInternal(child, index, params); + } + protected void addViewInternal(View child, int index, LayoutParams params) { if (child.parent == this) return; @@ -81,10 +85,13 @@ public class ViewGroup extends View implements ViewParent, ViewManager { requestLayout(); } - public void addView(View child, int index, LayoutParams params) { - addViewInternal(child, index, params); + /* We never call this ourselves */ + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + return native_dispatchTouchEvent(widget, event, event.getX(), event.getY()); } + protected boolean addViewInLayout(View child, int index, LayoutParams params) { addViewInternal(child, index, params); return true; @@ -169,6 +176,16 @@ public class ViewGroup extends View implements ViewParent, ViewManager { } } + @Override + protected void dispatchVisibilityChanged(View changedView, int visibility) { + if (children == null) // happens if this gets called during super constructor + return; + + for (View child: children) { + child.dispatchVisibilityChanged(changedView, visibility); + } + } + protected native void native_addView(long widget, long child, int index, LayoutParams params); protected native void native_removeView(long widget, long child); @Override @@ -636,4 +653,6 @@ public class ViewGroup extends View implements ViewParent, ViewManager { } public void requestChildFocus(View child, View focused) {} + + public native boolean native_dispatchTouchEvent(long widget, MotionEvent event, double x, double y); }