handle overwritten onDraw function in WrapperWidget

This makes it easy to support for example ImageView with custom
onDraw function
This commit is contained in:
Julian Winkler
2023-07-14 17:53:12 +02:00
parent 22dcaf45e8
commit def91a688d
7 changed files with 80 additions and 48 deletions

View File

@@ -13,6 +13,7 @@
#define _PTR(ptr)((void*)(intptr_t)(ptr)) #define _PTR(ptr)((void*)(intptr_t)(ptr))
#define _INTPTR(ptr)((jlong)(intptr_t)(ptr)) #define _INTPTR(ptr)((jlong)(intptr_t)(ptr))
#define _REF(obj)((*env)->NewGlobalRef(env, obj)) #define _REF(obj)((*env)->NewGlobalRef(env, obj))
#define _UNREF(obj)((*env)->DeleteGlobalRef(env, obj))
#define _CLASS(object) ((*env)->GetObjectClass(env, object)) #define _CLASS(object) ((*env)->GetObjectClass(env, object))
#define _SUPER(object) ((*env)->GetSuperclass(env, object)) #define _SUPER(object) ((*env)->GetSuperclass(env, object))
#define _METHOD(class, method, attrs) ((*env)->GetMethodID(env, class, method, attrs)) #define _METHOD(class, method, attrs) ((*env)->GetMethodID(env, class, method, attrs))

View File

@@ -104,6 +104,8 @@ void set_up_handle_cache(JNIEnv *env, char *apk_main_activity_class)
handle_cache.view.setLayoutParams = _METHOD(handle_cache.view.class, "setLayoutParams", "(Landroid/view/ViewGroup$LayoutParams;)V"); handle_cache.view.setLayoutParams = _METHOD(handle_cache.view.class, "setLayoutParams", "(Landroid/view/ViewGroup$LayoutParams;)V");
if((*env)->ExceptionCheck(env)) if((*env)->ExceptionCheck(env))
(*env)->ExceptionDescribe(env); (*env)->ExceptionDescribe(env);
handle_cache.view.onDraw = _METHOD(handle_cache.view.class, "onDraw", "(Landroid/graphics/Canvas;)V");
handle_cache.view.onMeasure = _METHOD(handle_cache.view.class, "onMeasure", "(II)V");
handle_cache.asset_manager.class = _REF((*env)->FindClass(env, "android/content/res/AssetManager")); 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"); handle_cache.asset_manager.extractFromAPK = _STATIC_METHOD(handle_cache.asset_manager.class, "extractFromAPK", "(Ljava/lang/String;Ljava/lang/String;)V");

View File

@@ -68,6 +68,8 @@ struct handle_cache {
struct { struct {
jclass class; jclass class;
jmethodID setLayoutParams; jmethodID setLayoutParams;
jmethodID onDraw;
jmethodID onMeasure;
} view; } view;
struct { struct {
jclass class; jclass class;

View File

@@ -166,43 +166,11 @@ JNIEXPORT void JNICALL Java_android_view_View_setVisibility(JNIEnv *env, jobject
} }
} }
// --- the stuff below only applies to widgets that override the OnDraw() method; other widgets are created by class-specific constructors.
// FIXME: how do we handle someone subclassing something other then View and then overriding the onDraw/onMeasure method(s)?
struct jni_callback_data { JavaVM *jvm; jobject this; jclass this_class; cairo_t *cached_cr; jobject canvas;};
static void draw_function(GtkDrawingArea *area, cairo_t *cr, int width, int height, struct jni_callback_data *d)
{
JNIEnv *env;
(*d->jvm)->GetEnv(d->jvm, (void**)&env, JNI_VERSION_1_6);
if(d->cached_cr != cr) {
if(d->canvas == NULL) {
d->canvas = _REF((*env)->NewObject(env, handle_cache.canvas.class, handle_cache.canvas.constructor, _INTPTR(cr), _INTPTR(area)));
} else {
_SET_LONG_FIELD(d->canvas, "cairo_context", _INTPTR(cr));
}
d->cached_cr = cr;
}
(*env)->CallVoidMethod(env, d->this, _METHOD(d->this_class, "onDraw", "(Landroid/graphics/Canvas;)V"), d->canvas);
if((*env)->ExceptionCheck(env))
(*env)->ExceptionDescribe(env);
}
static void on_mapped(GtkWidget* self, struct jni_callback_data *d)
{
JNIEnv *env;
(*d->jvm)->GetEnv(d->jvm, (void**)&env, JNI_VERSION_1_6);
(*env)->CallVoidMethod(env, d->this, _METHOD(d->this_class, "onMeasure", "(II)V"), gtk_widget_get_width(self), gtk_widget_get_height(self));
}
// FIXME: this is used in one other place as well, should probably go in util.c or gtk_util.c? // FIXME: this is used in one other place as well, should probably go in util.c or gtk_util.c?
gboolean tick_callback(GtkWidget* widget, GdkFrameClock* frame_clock, gpointer user_data) gboolean tick_callback(GtkWidget* widget, GdkFrameClock* frame_clock, gpointer user_data)
{ {
gtk_widget_queue_draw(widget); gtk_widget_queue_draw(widget);
gtk_widget_queue_draw(gtk_widget_get_parent(widget));
return G_SOURCE_CONTINUE; return G_SOURCE_CONTINUE;
} }
@@ -211,24 +179,10 @@ JNIEXPORT void JNICALL Java_android_view_View_native_1constructor(JNIEnv *env, j
GtkWidget *wrapper = wrapper_widget_new(); GtkWidget *wrapper = wrapper_widget_new();
GtkWidget *area = gtk_drawing_area_new(); GtkWidget *area = gtk_drawing_area_new();
wrapper_widget_set_child(WRAPPER_WIDGET(wrapper), area); wrapper_widget_set_child(WRAPPER_WIDGET(wrapper), area);
wrapper_widget_set_jobject(WRAPPER_WIDGET(wrapper), env, this);
JavaVM *jvm;
(*env)->GetJavaVM(env, &jvm);
struct jni_callback_data *callback_data = malloc(sizeof(struct jni_callback_data));
callback_data->jvm = jvm;
callback_data->this = _REF(this);
callback_data->this_class = _REF(_CLASS(this));
callback_data->cached_cr = NULL;
callback_data->canvas = NULL;
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(area), ( void(*)(GtkDrawingArea*,cairo_t*,int,int,gpointer) )draw_function, callback_data, NULL);
gtk_widget_add_tick_callback(area, tick_callback, NULL, NULL); gtk_widget_add_tick_callback(area, tick_callback, NULL, NULL);
// add a callback for when the widget is mapped, which will call onMeasure to figure out what size the widget wants to be
g_signal_connect(area, "map", G_CALLBACK(on_mapped), callback_data);
_SET_LONG_FIELD(this, "widget", (long)area); _SET_LONG_FIELD(this, "widget", (long)area);
} }

View File

@@ -1,5 +1,7 @@
#include <gtk/gtk.h> #include <gtk/gtk.h>
#include "../defines.h"
#include "../util.h"
#include "../drawables/ninepatch.h" #include "../drawables/ninepatch.h"
#include "WrapperWidget.h" #include "WrapperWidget.h"
@@ -52,6 +54,15 @@ static void wrapper_widget_init (WrapperWidget *wrapper_widget)
static void wrapper_widget_dispose(GObject *wrapper_widget) static void wrapper_widget_dispose(GObject *wrapper_widget)
{ {
gtk_widget_unparent(gtk_widget_get_first_child(GTK_WIDGET(wrapper_widget))); gtk_widget_unparent(gtk_widget_get_first_child(GTK_WIDGET(wrapper_widget)));
WrapperWidget *wrapper = WRAPPER_WIDGET(wrapper_widget);
if (wrapper->jvm) {
JNIEnv *env;
(*wrapper->jvm)->GetEnv(wrapper->jvm, (void**)&env, JNI_VERSION_1_6);
if (wrapper->jobj)
_UNREF(wrapper->jobj);
if (wrapper->canvas)
_UNREF(wrapper->canvas);
}
G_OBJECT_CLASS (wrapper_widget_parent_class)->dispose (wrapper_widget); G_OBJECT_CLASS (wrapper_widget_parent_class)->dispose (wrapper_widget);
} }
@@ -78,6 +89,29 @@ void wrapper_snapshot(GtkWidget* widget, GtkSnapshot* snapshot)
g_object_unref(texture); g_object_unref(texture);
} }
gtk_widget_snapshot_child(widget, gtk_widget_get_first_child(widget), snapshot); gtk_widget_snapshot_child(widget, gtk_widget_get_first_child(widget), snapshot);
// if onDraw method is overwritten call it now
WrapperWidget *wrapper_widget = WRAPPER_WIDGET(widget);
GtkAllocation alloc;
gtk_widget_get_allocation(widget, &alloc);
if (wrapper_widget->draw_method) {
cairo_t *cr = gtk_snapshot_append_cairo (snapshot, &GRAPHENE_RECT_INIT(0, 0, alloc.width, alloc.height));
JNIEnv *env;
(*wrapper_widget->jvm)->GetEnv(wrapper_widget->jvm, (void**)&env, JNI_VERSION_1_6);
if(wrapper_widget->canvas == NULL) {
wrapper_widget->canvas = _REF((*env)->NewObject(env, handle_cache.canvas.class, handle_cache.canvas.constructor, _INTPTR(cr), 0));
} else {
_SET_LONG_FIELD(wrapper_widget->canvas, "cairo_context", _INTPTR(cr));
}
(*env)->CallVoidMethod(env, wrapper_widget->jobj, wrapper_widget->draw_method, wrapper_widget->canvas);
if((*env)->ExceptionCheck(env))
(*env)->ExceptionDescribe(env);
cairo_destroy (cr);
}
} }
@@ -102,3 +136,32 @@ void wrapper_widget_set_child(WrapperWidget *parent, GtkWidget *child) // TODO:
{ {
gtk_widget_insert_before(child, GTK_WIDGET(parent), NULL); gtk_widget_insert_before(child, GTK_WIDGET(parent), NULL);
} }
static void on_mapped(GtkWidget* self, gpointer data)
{
WrapperWidget *wrapper = WRAPPER_WIDGET(self);
if (wrapper->jvm) {
JNIEnv *env;
(*wrapper->jvm)->GetEnv(wrapper->jvm, (void**)&env, JNI_VERSION_1_6);
(*env)->CallVoidMethod(env, wrapper->jobj, wrapper->measure_method, gtk_widget_get_width(self), gtk_widget_get_height(self));
}
}
void wrapper_widget_set_jobject(WrapperWidget *wrapper, JNIEnv *env, jobject jobj)
{
JavaVM *jvm;
(*env)->GetJavaVM(env, &jvm);
wrapper->jvm = jvm;
wrapper->jobj = _REF(jobj);
jmethodID draw_method = _METHOD(_CLASS(jobj), "onDraw", "(Landroid/graphics/Canvas;)V");
if (draw_method != handle_cache.view.onDraw)
wrapper->draw_method = draw_method;
jmethodID measure_method = _METHOD(_CLASS(jobj), "onMeasure", "(II)V");
if (measure_method != handle_cache.view.onMeasure) {
wrapper->measure_method = measure_method;
// add a callback for when the widget is mapped, which will call onMeasure to figure out what size the widget wants to be
g_signal_connect(wrapper, "map", G_CALLBACK(on_mapped), NULL);
}
}

View File

@@ -1,11 +1,18 @@
#ifndef WRAPPER_WIDGET_H #ifndef WRAPPER_WIDGET_H
#define WRAPPER_WIDGET_H #define WRAPPER_WIDGET_H
#include <jni.h>
G_DECLARE_FINAL_TYPE (WrapperWidget, wrapper_widget, WRAPPER, WIDGET, GtkWidget) G_DECLARE_FINAL_TYPE (WrapperWidget, wrapper_widget, WRAPPER, WIDGET, GtkWidget)
struct _WrapperWidget struct _WrapperWidget
{ {
GtkWidget parent_instance; GtkWidget parent_instance;
JavaVM *jvm;
jobject jobj;
jobject canvas;
jmethodID draw_method;
jmethodID measure_method;
}; };
struct _WrapperWidgetClass struct _WrapperWidgetClass
@@ -15,5 +22,6 @@ struct _WrapperWidgetClass
GtkWidget * wrapper_widget_new(void); GtkWidget * wrapper_widget_new(void);
void wrapper_widget_set_child(WrapperWidget *parent, GtkWidget *child); void wrapper_widget_set_child(WrapperWidget *parent, GtkWidget *child);
void wrapper_widget_set_jobject(WrapperWidget *wrapper, JNIEnv *env, jobject jobj);
#endif #endif

View File

@@ -12,6 +12,7 @@ JNIEXPORT void JNICALL Java_android_widget_ImageView_native_1constructor__Landro
GtkWidget *wrapper = wrapper_widget_new(); GtkWidget *wrapper = wrapper_widget_new();
GtkWidget *image = gtk_picture_new_for_resource("/org/gtk/libgtk/icons/16x16/status/image-missing.png"); // show "broken image" icon GtkWidget *image = gtk_picture_new_for_resource("/org/gtk/libgtk/icons/16x16/status/image-missing.png"); // show "broken image" icon
wrapper_widget_set_child(WRAPPER_WIDGET(wrapper), image); wrapper_widget_set_child(WRAPPER_WIDGET(wrapper), image);
wrapper_widget_set_jobject(WRAPPER_WIDGET(wrapper), env, this);
_SET_LONG_FIELD(this, "widget", _INTPTR(image));} _SET_LONG_FIELD(this, "widget", _INTPTR(image));}
JNIEXPORT void JNICALL Java_android_widget_ImageView_native_1constructor__Landroid_content_Context_2(JNIEnv *env, jobject this, jobject context) JNIEXPORT void JNICALL Java_android_widget_ImageView_native_1constructor__Landroid_content_Context_2(JNIEnv *env, jobject this, jobject context)
@@ -19,6 +20,7 @@ JNIEXPORT void JNICALL Java_android_widget_ImageView_native_1constructor__Landro
GtkWidget *wrapper = wrapper_widget_new(); GtkWidget *wrapper = wrapper_widget_new();
GtkWidget *image = gtk_picture_new_for_resource("/org/gtk/libgtk/icons/16x16/status/image-missing.png"); // show "broken image" icon GtkWidget *image = gtk_picture_new_for_resource("/org/gtk/libgtk/icons/16x16/status/image-missing.png"); // show "broken image" icon
wrapper_widget_set_child(WRAPPER_WIDGET(wrapper), image); wrapper_widget_set_child(WRAPPER_WIDGET(wrapper), image);
wrapper_widget_set_jobject(WRAPPER_WIDGET(wrapper), env, this);
_SET_LONG_FIELD(this, "widget", _INTPTR(image)); _SET_LONG_FIELD(this, "widget", _INTPTR(image));
} }