Files
android_translation_layer/src/api-impl-jni/views/android_view_View.c
2024-08-02 17:02:53 +02:00

579 lines
22 KiB
C

#include <assert.h>
#include <stdio.h>
#include <gtk/gtk.h>
#include "../defines.h"
#include "../util.h"
#include "../widgets/WrapperWidget.h"
#include "../views/AndroidLayout.h"
#include "../generated_headers/android_view_View.h"
#define SOURCE_TOUCHSCREEN 0x1002
static GdkEvent *canceled_event = NULL;
static WrapperWidget *cancel_triggerer = NULL;
static bool call_ontouch_callback(WrapperWidget *wrapper, int action, double x, double y, GtkPropagationPhase phase, guint32 timestamp, GdkEvent *event)
{
bool ret;
double raw_x;
double raw_y;
JNIEnv *env = get_jni_env();
jobject this = wrapper->jobj;
gdk_event_get_position(event, &raw_x, &raw_y);
jobject motion_event = (*env)->NewObject(env, handle_cache.motion_event.class, handle_cache.motion_event.constructor, SOURCE_TOUCHSCREEN, action, (float)x, (float)y, (long)timestamp, (float)raw_x, (float)raw_y);
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);
(*env)->DeleteLocalRef(env, motion_event);
if (action == MOTION_EVENT_ACTION_UP)
wrapper->intercepting_touch = false;
return ret;
}
static void gdk_event_get_widget_relative_position(GdkEvent *event, GtkWidget *widget, double *x, double *y)
{
int ret;
graphene_point_t p;
double off_x;
double off_y;
gdk_event_get_position(event, x, y);
GtkWidget *window = GTK_WIDGET(gtk_widget_get_native(widget));
gtk_native_get_surface_transform(GTK_NATIVE(window), &off_x, &off_y);
ret = gtk_widget_compute_point(window, widget, &GRAPHENE_POINT_INIT(*x - off_x, *y - off_y), &p);
assert(ret);
*x = p.x;
*y = p.y;
}
// TODO: find a way to reconcile this with libandroid/input.c?
static gboolean on_event(GtkEventControllerLegacy *event_controller, GdkEvent *event, gpointer user_data)
{
double x;
double y;
GtkWidget *widget = gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(event_controller));
WrapperWidget *wrapper = WRAPPER_WIDGET(widget);
GtkPropagationPhase phase = gtk_event_controller_get_propagation_phase(GTK_EVENT_CONTROLLER(event_controller));
guint32 timestamp = gdk_event_get_time(event);
// TODO: this doesn't work for multitouch
if (cancel_triggerer == wrapper) { // cancel done
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, MOTION_EVENT_ACTION_CANCEL, x, y, phase, timestamp, event);
return false;
}
switch(gdk_event_get_event_type(event)) {
case GDK_BUTTON_PRESS:
case GDK_TOUCH_BEGIN:
gdk_event_get_widget_relative_position(event, widget, &x, &y);
return call_ontouch_callback(wrapper, MOTION_EVENT_ACTION_DOWN, x, y, phase, timestamp, event);
break;
case GDK_BUTTON_RELEASE:
case GDK_TOUCH_END:
gdk_event_get_widget_relative_position(event, widget, &x, &y);
return call_ontouch_callback(wrapper, MOTION_EVENT_ACTION_UP, x, y, phase, timestamp, event);
break;
case GDK_MOTION_NOTIFY:
if (!(gdk_event_get_modifier_state(event) & GDK_BUTTON1_MASK))
break;
case GDK_TOUCH_UPDATE:
gdk_event_get_widget_relative_position(event, widget, &x, &y);
return call_ontouch_callback(wrapper, MOTION_EVENT_ACTION_MOVE, x, y, phase, timestamp, event);
break;
default:
break;
}
return false;
}
static void on_click(GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data)
{
JNIEnv *env = get_jni_env();
GtkWidget *widget = gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(gesture));
WrapperWidget *wrapper = WRAPPER_WIDGET(gtk_widget_get_parent(widget));
(*env)->CallBooleanMethod(env, wrapper->jobj, handle_cache.view.performClick);
if((*env)->ExceptionCheck(env))
(*env)->ExceptionDescribe(env);
}
#define SOURCE_CLASS_POINTER 0x2
#define MAGIC_SCROLL_FACTOR 32
static gboolean scroll_cb(GtkEventControllerScroll* self, gdouble dx, gdouble dy, jobject this)
{
JNIEnv *env = get_jni_env();
GdkScrollUnit scroll_unit = gtk_event_controller_scroll_get_unit (self);
if (scroll_unit == GDK_SCROLL_UNIT_SURFACE) {
dx /= MAGIC_SCROLL_FACTOR;
dy /= MAGIC_SCROLL_FACTOR;
}
jobject motion_event = (*env)->NewObject(env, handle_cache.motion_event.class, handle_cache.motion_event.constructor, SOURCE_CLASS_POINTER, MOTION_EVENT_ACTION_SCROLL, dx, -dy, (long)0, 0.f, 0.f);
gboolean ret = (*env)->CallBooleanMethod(env, this, handle_cache.view.onGenericMotionEvent, motion_event);
if((*env)->ExceptionCheck(env))
(*env)->ExceptionDescribe(env);
return ret;
}
void _setOnTouchListener(JNIEnv *env, jobject this, GtkWidget *widget)
{
GtkEventController *old_controller = g_object_get_data(G_OBJECT(widget), "on_touch_listener");
if(old_controller)
return;
GtkEventController *controller = GTK_EVENT_CONTROLLER(gtk_event_controller_legacy_new());
gtk_event_controller_set_propagation_phase(controller, GTK_PHASE_BUBBLE);
g_signal_connect(controller, "event", G_CALLBACK(on_event), NULL);
gtk_widget_add_controller(widget, controller);
g_object_set_data(G_OBJECT(widget), "on_touch_listener", controller);
jmethodID onintercepttouchevent_method = _METHOD(_CLASS(this), "onInterceptTouchEvent", "(Landroid/view/MotionEvent;)Z");
if (onintercepttouchevent_method != handle_cache.view.onInterceptTouchEvent) {
GtkEventController *old_controller = g_object_get_data(G_OBJECT(widget), "on_intercept_touch_listener");
if(old_controller)
gtk_widget_remove_controller(widget, old_controller);
GtkEventController *controller = GTK_EVENT_CONTROLLER(gtk_event_controller_legacy_new());
gtk_event_controller_set_propagation_phase(controller, GTK_PHASE_CAPTURE);
g_signal_connect(controller, "event", G_CALLBACK(on_event), NULL);
gtk_widget_add_controller(widget, controller);
g_object_set_data(G_OBJECT(widget), "on_intercept_touch_listener", controller);
}
WrapperWidget *wrapper = WRAPPER_WIDGET(widget);
if (!wrapper->needs_allocation && (wrapper->layout_width || wrapper->layout_height))
gtk_widget_size_allocate(GTK_WIDGET(wrapper), &(GtkAllocation){.x = 0, .y = 0, .width = wrapper->layout_width, .height = wrapper->layout_height}, 0);
wrapper->needs_allocation = true;
gtk_widget_set_overflow(GTK_WIDGET(wrapper), GTK_OVERFLOW_HIDDEN);
}
JNIEXPORT void JNICALL Java_android_view_View_nativeSetOnTouchListener(JNIEnv *env, jobject this, jlong widget_ptr)
{
GtkWidget *widget = gtk_widget_get_parent(GTK_WIDGET(_PTR(widget_ptr)));
_setOnTouchListener(env, this, widget);
}
JNIEXPORT void JNICALL Java_android_view_View_nativeSetOnClickListener(JNIEnv *env, jobject this, jlong widget_ptr)
{
GtkWidget *widget = GTK_WIDGET(_PTR(widget_ptr));
GtkEventController *old_controller = g_object_get_data(G_OBJECT(widget), "on_click_listener");
if(old_controller)
return;
GtkEventController *controller = GTK_EVENT_CONTROLLER(gtk_gesture_click_new());
g_signal_connect(controller, "released", G_CALLBACK(on_click), NULL); // the release completes the click, I guess?
gtk_widget_add_controller(widget, controller);
g_object_set_data(G_OBJECT(widget), "on_click_listener", controller);
widget_set_needs_allocation(widget);
}
JNIEXPORT jint JNICALL Java_android_view_View_getWidth(JNIEnv *env, jobject this)
{
GtkWidget *widget = GTK_WIDGET(_PTR(_GET_LONG_FIELD(this, "widget")));
/* FIXME: is this needed in Gtk4?
GtkAllocation alloc;
gtk_widget_get_allocation(widget, &alloc);
printf("widget size is currently %dx%d\n", alloc.width, alloc.height);
*/
if (ATL_IS_ANDROID_LAYOUT(gtk_widget_get_layout_manager(widget))) {
AndroidLayout *layout = ATL_ANDROID_LAYOUT(gtk_widget_get_layout_manager(widget));
return layout->real_width;
}
return gtk_widget_get_width(widget);
}
JNIEXPORT jint JNICALL Java_android_view_View_getHeight(JNIEnv *env, jobject this)
{
GtkWidget *widget = GTK_WIDGET(_PTR(_GET_LONG_FIELD(this, "widget")));
if (ATL_IS_ANDROID_LAYOUT(gtk_widget_get_layout_manager(widget))) {
AndroidLayout *layout = ATL_ANDROID_LAYOUT(gtk_widget_get_layout_manager(widget));
return layout->real_height;
}
return gtk_widget_get_height(widget);
}
#define GRAVITY_TOP (1<<5)//0x30
#define GRAVITY_BOTTOM (1<<6)//0x50
#define GRAVITY_LEFT (1<<1)//0x3
#define GRAVITY_RIGHT (1<<2)//0x5
#define GRAVITY_CENTER_VERTICAL 0x10
#define GRAVITY_CENTER_HORIZONTAL 0x01
#define GRAVITY_CENTER (GRAVITY_CENTER_VERTICAL | GRAVITY_CENTER_HORIZONTAL)
#define MATCH_PARENT (-1)
JNIEXPORT void JNICALL Java_android_view_View_native_1setLayoutParams(JNIEnv *env, jobject this, jlong widget_ptr, jint width, jint height, jint gravity, jfloat weight,
jint leftMargin, jint topMargin, jint rightMargin, jint bottomMargin)
{
GtkWidget *widget = gtk_widget_get_parent(GTK_WIDGET(_PTR(widget_ptr)));
GtkAlign halign = GTK_ALIGN_FILL;
GtkAlign valign = GTK_ALIGN_FILL;
gboolean hexpand = FALSE;
gboolean vexpand = FALSE;
if (gravity != -1) {
if(gravity & GRAVITY_BOTTOM)
valign = GTK_ALIGN_END;
else if(gravity & GRAVITY_TOP)
valign = GTK_ALIGN_START;
else
valign = GTK_ALIGN_FILL;
if(gravity & GRAVITY_RIGHT)
halign = GTK_ALIGN_END;
else if(gravity & GRAVITY_LEFT)
halign = GTK_ALIGN_START;
else
halign = GTK_ALIGN_FILL;
if(gravity == GRAVITY_CENTER) {
valign = GTK_ALIGN_CENTER; // GTK_ALIGN_CENTER doesn't seem to be the right one?
halign = GTK_ALIGN_CENTER; // ditto (GTK_ALIGN_CENTER)
hexpand = TRUE; // haxx or not?
vexpand = TRUE; // seems to be the deciding factor for whether to expand, guess I should try on android
}
}
if (weight > 0.f) {
printf(":::-: setting weight: %f\n", weight);
hexpand = TRUE;
vexpand = TRUE;
}
if (width == MATCH_PARENT) {
hexpand = true;
halign = GTK_ALIGN_FILL;
}
if (height == MATCH_PARENT) {
vexpand = true;
valign = GTK_ALIGN_FILL;
}
gtk_widget_set_hexpand(widget, hexpand);
gtk_widget_set_vexpand(widget, vexpand);
gtk_widget_set_halign(widget, halign);
gtk_widget_set_valign(widget, valign);
if(width > 0)
g_object_set(G_OBJECT(widget), "width-request", width, NULL);
if(height > 0)
g_object_set(G_OBJECT(widget), "height-request", height, NULL);
GtkWidget *parent = gtk_widget_get_parent(widget);
// if parent is Java widget, it will handle the margins by itself
if (parent && !ATL_IS_ANDROID_LAYOUT(gtk_widget_get_layout_manager(parent))) {
gtk_widget_set_margin_start(widget, leftMargin);
gtk_widget_set_margin_top(widget, topMargin);
gtk_widget_set_margin_end(widget, rightMargin);
gtk_widget_set_margin_bottom(widget, bottomMargin);
}
GtkLayoutManager *layout_manager = gtk_widget_get_layout_manager(WRAPPER_WIDGET(widget)->child);
if (ATL_IS_ANDROID_LAYOUT(layout_manager))
android_layout_set_params(ATL_ANDROID_LAYOUT(layout_manager), width, height);
wrapper_widget_set_layout_params(WRAPPER_WIDGET(widget), width, height);
}
JNIEXPORT void JNICALL Java_android_view_View_native_1setVisibility(JNIEnv *env, jobject this, jlong widget_ptr, jint visibility, jfloat alpha) {
GtkWidget *widget = gtk_widget_get_parent(GTK_WIDGET(_PTR(widget_ptr)));
gtk_widget_set_visible(widget, visibility != android_view_View_GONE);
gtk_widget_set_opacity(widget, (visibility != android_view_View_INVISIBLE) * alpha);
gtk_widget_set_sensitive(widget, visibility != android_view_View_INVISIBLE && alpha != 0.0f);
}
/** JavaWidget:
* Minimal gtk widget class which does nothing.
* Drawing will be overwritten by WrapperWidget.
* If it holds children, they will be layouted by AndroidLayout
*/
struct _JavaWidget {GtkWidget parent_instance;};
G_DECLARE_FINAL_TYPE(JavaWidget, java_widget, JAVA, WIDGET, GtkWidget)
static void java_widget_dispose(GObject *java_widget)
{
GtkWidget *widget = GTK_WIDGET(java_widget);
GtkWidget *child = gtk_widget_get_first_child(widget);
while (child) {
GtkWidget *_child = gtk_widget_get_next_sibling(child);
gtk_widget_unparent(child);
child = _child;
}
}
static void java_widget_init(JavaWidget *java_widget) {}
static void java_widget_class_init(JavaWidgetClass *class) {
G_OBJECT_CLASS(class)->dispose = java_widget_dispose;
}
G_DEFINE_TYPE(JavaWidget, java_widget, GTK_TYPE_WIDGET)
JNIEXPORT jlong JNICALL Java_android_view_View_native_1constructor(JNIEnv *env, jobject this, jobject context, jobject attrs)
{
WrapperWidget *wrapper = g_object_ref(WRAPPER_WIDGET(wrapper_widget_new()));
GtkWidget *widget = g_object_new(java_widget_get_type(), NULL);
gtk_widget_set_layout_manager(widget, android_layout_new(this));
wrapper_widget_set_child(WRAPPER_WIDGET(wrapper), widget);
wrapper_widget_set_jobject(WRAPPER_WIDGET(wrapper), env, this);
jclass class = _CLASS(this);
jstring nameObj = (*env)->CallObjectMethod(env, class,
_METHOD(_CLASS(class), "getName", "()Ljava/lang/String;"));
const char *name = (*env)->GetStringUTFChars(env, nameObj, NULL);
gtk_widget_set_name(widget, name);
(*env)->ReleaseStringUTFChars(env, nameObj, name);
if (_METHOD(_CLASS(this), "onGenericMotionEvent", "(Landroid/view/MotionEvent;)Z") != handle_cache.view.onGenericMotionEvent) {
GtkEventController *controller = gtk_event_controller_scroll_new(GTK_EVENT_CONTROLLER_SCROLL_VERTICAL);
g_signal_connect(controller, "scroll", G_CALLBACK(scroll_cb), wrapper->jobj);
gtk_widget_add_controller(widget, controller);
}
return _INTPTR(widget);
}
JNIEXPORT void JNICALL Java_android_view_View_nativeInvalidate(JNIEnv *env, jclass class, jlong widget_ptr) {
GtkWidget *widget = GTK_WIDGET(_PTR(widget_ptr));
wrapper_widget_queue_draw(WRAPPER_WIDGET(gtk_widget_get_parent(widget)));
}
JNIEXPORT void JNICALL Java_android_view_View_native_1destructor(JNIEnv *env, jobject this, jlong widget_ptr)
{
g_object_unref(gtk_widget_get_parent(_PTR(widget_ptr)));
}
#define MEASURE_SPEC_UNSPECIFIED (0 << 30)
#define MEASURE_SPEC_EXACTLY (1 << 30)
#define MEASURE_SPEC_AT_MOST (2 << 30)
#define MEASURE_SPEC_MASK (0x3 << 30)
JNIEXPORT void JNICALL Java_android_view_View_native_1measure(JNIEnv *env, jobject this, jlong widget_ptr, jint width_spec, jint height_spec) {
int width = -1;
int height = -1;
GtkWidget *widget = gtk_widget_get_parent(GTK_WIDGET(_PTR(widget_ptr)));
int width_spec_size = width_spec & ~MEASURE_SPEC_MASK;
int height_spec_size = height_spec & ~MEASURE_SPEC_MASK;
int width_spec_mode = width_spec & MEASURE_SPEC_MASK;
int height_spec_mode = height_spec & MEASURE_SPEC_MASK;
GtkSizeRequestMode request_mode;
if (width_spec_mode == MEASURE_SPEC_EXACTLY) {
width = width_spec_size;
request_mode = GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
}
if (height_spec_mode == MEASURE_SPEC_EXACTLY) {
height = height_spec_size;
request_mode = GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT;
}
if (width == -1 && height == -1)
request_mode = gtk_widget_get_request_mode(widget);
if (width == -1 && request_mode == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH) {
gtk_widget_measure(widget, GTK_ORIENTATION_HORIZONTAL, height, NULL, &width, NULL, NULL);
if (width_spec_mode == MEASURE_SPEC_AT_MOST && width > width_spec_size)
width = width_spec_size;
}
if (height == -1) {
gtk_widget_measure(widget, GTK_ORIENTATION_VERTICAL, width, NULL, &height, NULL, NULL);
if (height_spec_mode == MEASURE_SPEC_AT_MOST && height > height_spec_size)
height = height_spec_size;
}
if (width == -1) {
gtk_widget_measure(widget, GTK_ORIENTATION_HORIZONTAL, height, NULL, &width, NULL, NULL);
if (width_spec_mode == MEASURE_SPEC_AT_MOST && width > width_spec_size)
width = width_spec_size;
}
(*env)->CallVoidMethod(env, this, handle_cache.view.setMeasuredDimension, width, height);
}
JNIEXPORT void JNICALL Java_android_view_View_native_1layout(JNIEnv *env, jobject this, jlong widget_ptr, jint l, jint t, jint r, jint b) {
GtkWidget *widget = gtk_widget_get_parent(GTK_WIDGET(_PTR(widget_ptr)));
GtkAllocation allocation = {
.x=l,
.y=t,
};
int width = r-l;
int height = b-t;
WrapperWidget *wrapper = WRAPPER_WIDGET(widget);
if (wrapper->real_width != width || wrapper->real_height != height) {
wrapper->real_width = width;
wrapper->real_height = height;
if (!wrapper->needs_allocation)
gtk_widget_queue_allocate(widget);
}
if (wrapper->needs_allocation) {
allocation.width = width;
allocation.height = height;
}
gtk_widget_size_allocate(widget, &allocation, -1);
}
JNIEXPORT void JNICALL Java_android_view_View_native_1requestLayout(JNIEnv *env, jobject this, jlong widget_ptr) {
GtkWidget *widget = GTK_WIDGET(_PTR(widget_ptr));
gtk_widget_queue_resize(widget);
}
/* we kinda need per-widget css */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
JNIEXPORT void JNICALL Java_android_view_View_setBackgroundColor(JNIEnv *env, jobject this, jint color)
{
GtkWidget *widget = GTK_WIDGET(_PTR(_GET_LONG_FIELD(this, "widget")));
GtkStyleContext *style_context = gtk_widget_get_style_context(widget);
GtkCssProvider *old_provider = g_object_get_data(G_OBJECT(widget), "background_color_style_provider");
if(old_provider)
gtk_style_context_remove_provider(style_context, GTK_STYLE_PROVIDER(old_provider));
GtkCssProvider *css_provider = gtk_css_provider_new();
char *css_string = g_markup_printf_escaped("* { background-image: none; background-color: #%06x%02x; }", color & 0xFFFFFF, (color >> 24) & 0xFF);
#if GTK_CHECK_VERSION(4, 12, 0)
gtk_css_provider_load_from_string(css_provider, css_string);
#else
gtk_css_provider_load_from_data(css_provider, css_string, strlen(css_string));
#endif
g_free(css_string);
gtk_style_context_add_provider(style_context, GTK_STYLE_PROVIDER(css_provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
g_object_set_data(G_OBJECT(widget), "background_color_style_provider", css_provider);
widget_set_needs_allocation(widget);
}
#pragma GCC diagnostic pop
JNIEXPORT void JNICALL Java_android_view_View_native_1setBackgroundDrawable(JNIEnv *env, jobject this, jlong widget_ptr, jlong paintable_ptr) {
GtkWidget *widget = GTK_WIDGET(_PTR(widget_ptr));
GdkPaintable *paintable = GDK_PAINTABLE(_PTR(paintable_ptr));
wrapper_widget_set_background(WRAPPER_WIDGET(gtk_widget_get_parent(widget)), paintable);
}
JNIEXPORT jboolean JNICALL Java_android_view_View_native_1getGlobalVisibleRect(JNIEnv *env, jobject this, jlong widget_ptr, jobject rect) {
GtkWidget *widget = gtk_widget_get_parent(GTK_WIDGET(_PTR(widget_ptr)));
graphene_point_t point_in = {0, 0};
graphene_point_t point_out;
double off_x;
double off_y;
gboolean ret;
GtkWidget *window = GTK_WIDGET(gtk_widget_get_native(widget));
gtk_native_get_surface_transform(GTK_NATIVE(window), &off_x, &off_y);
ret = gtk_widget_compute_point(widget, window, &point_in, &point_out);
if (!ret)
return false;
_SET_INT_FIELD(rect, "left", point_out.x + off_x);
_SET_INT_FIELD(rect, "top", point_out.y + off_y);
point_in = (graphene_point_t){Java_android_view_View_getWidth(env, this), Java_android_view_View_getHeight(env, this)};
ret = gtk_widget_compute_point(widget, window, &point_in, &point_out);
if (!ret)
return false;
_SET_INT_FIELD(rect, "right", point_out.x + off_x);
_SET_INT_FIELD(rect, "bottom", point_out.y + off_y);
return true;
}
static void on_long_click(GtkGestureLongPress *gesture, double x, double y, gpointer user_data)
{
JNIEnv *env = get_jni_env();
GtkWidget *widget = gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(gesture));
WrapperWidget *wrapper = WRAPPER_WIDGET(gtk_widget_get_parent(widget));
bool ret =(*env)->CallBooleanMethod(env, wrapper->jobj, handle_cache.view.performLongClick, x, y);
if((*env)->ExceptionCheck(env))
(*env)->ExceptionDescribe(env);
if (ret)
gtk_gesture_set_state(GTK_GESTURE(gesture), GTK_EVENT_SEQUENCE_CLAIMED);
}
static void on_long_click_update(GtkGesture *gesture, GdkEventSequence* sequence, gpointer user_data)
{
GdkEvent *event = gtk_gesture_get_last_event(gesture, sequence);
if (event == canceled_event) {
gtk_event_controller_reset(GTK_EVENT_CONTROLLER(gesture));
}
}
JNIEXPORT void JNICALL Java_android_view_View_nativeSetOnLongClickListener(JNIEnv *env, jobject this, jlong widget_ptr)
{
GtkWidget *widget = GTK_WIDGET(_PTR(widget_ptr));
GtkEventController *old_controller = g_object_get_data(G_OBJECT(widget), "on_long_click_listener");
if(old_controller)
return;
GtkEventController *controller = GTK_EVENT_CONTROLLER(gtk_gesture_long_press_new());
g_signal_connect(controller, "pressed", G_CALLBACK(on_long_click), NULL);
g_signal_connect(controller, "update", G_CALLBACK(on_long_click_update), NULL);
gtk_widget_add_controller(widget, controller);
g_object_set_data(G_OBJECT(widget), "on_long_click_listener", controller);
}
JNIEXPORT jboolean JNICALL Java_android_view_View_native_1getMatrix(JNIEnv *env, jobject this, jlong widget_ptr, jlong matrix_ptr)
{
GtkWidget *widget = gtk_widget_get_parent(GTK_WIDGET(_PTR(widget_ptr)));
return gtk_widget_compute_transform(gtk_widget_get_parent(widget), widget, _PTR(matrix_ptr));
}
JNIEXPORT void JNICALL Java_android_view_View_native_1queueAllocate(JNIEnv *env, jobject this, jlong widget_ptr)
{
gtk_widget_queue_allocate(GTK_WIDGET(_PTR(widget_ptr)));
}
JNIEXPORT void JNICALL Java_android_view_View_native_1drawBackground(JNIEnv *env, jobject this, jlong widget_ptr, jlong snapshot_ptr)
{
WrapperWidget *wrapper = WRAPPER_WIDGET(gtk_widget_get_parent(GTK_WIDGET(_PTR(widget_ptr))));
GdkSnapshot *snapshot = GDK_SNAPSHOT(_PTR(snapshot_ptr));
if (wrapper->background)
gtk_widget_snapshot_child(&wrapper->parent_instance, wrapper->background, snapshot);
}
JNIEXPORT void JNICALL Java_android_view_View_native_1drawContent(JNIEnv *env, jobject this, jlong widget_ptr, jlong snapshot_ptr)
{
WrapperWidget *wrapper = WRAPPER_WIDGET(gtk_widget_get_parent(GTK_WIDGET(_PTR(widget_ptr))));
GdkSnapshot *snapshot = GDK_SNAPSHOT(_PTR(snapshot_ptr));
gtk_widget_snapshot_child(&wrapper->parent_instance, wrapper->child, snapshot);
}