2022-10-02 23:06:56 +02:00
# include <gtk/gtk.h>
# include "../defines.h"
# include "../util.h"
# include "../widgets/WrapperWidget.h"
2023-10-31 22:56:22 +01:00
# include "../views/AndroidLayout.h"
2022-10-02 23:06:56 +02:00
# include "../generated_headers/android_view_View.h"
2023-10-17 21:33:59 +02:00
# define SOURCE_TOUCHSCREEN 0x1002
2023-08-22 18:08:16 +02:00
2023-11-03 22:16:57 +01:00
struct touch_callback_data { JavaVM * jvm ; jobject this ; jobject on_touch_listener ; jclass on_touch_listener_class ; } ;
2022-10-02 23:06:56 +02:00
2023-10-28 16:52:58 +02:00
static bool call_ontouch_callback ( int action , double x , double y , struct touch_callback_data * d )
2022-10-02 23:06:56 +02:00
{
2023-10-28 16:52:58 +02:00
bool ret ;
2022-10-02 23:06:56 +02:00
JNIEnv * env ;
( * d - > jvm ) - > GetEnv ( d - > jvm , ( void * * ) & env , JNI_VERSION_1_6 ) ;
2023-10-17 21:33:59 +02:00
jobject motion_event = ( * env ) - > NewObject ( env , handle_cache . motion_event . class , handle_cache . motion_event . constructor , SOURCE_TOUCHSCREEN , action , ( float ) x , ( float ) y ) ;
2022-10-02 23:06:56 +02:00
2023-10-17 21:33:59 +02:00
/* NULL listener means the callback was registered for onTouchEvent */
if ( d - > on_touch_listener )
2023-10-28 16:52:58 +02:00
ret = ( * env ) - > CallBooleanMethod ( env , d - > on_touch_listener , _METHOD ( d - > on_touch_listener_class , " onTouch " , " (Landroid/view/View;Landroid/view/MotionEvent;)Z " ) , d - > this , motion_event ) ;
2023-10-17 21:33:59 +02:00
else
2023-10-28 16:52:58 +02:00
ret = ( * env ) - > CallBooleanMethod ( env , d - > this , _METHOD ( d - > on_touch_listener_class , " onTouchEvent " , " (Landroid/view/MotionEvent;)Z " ) , motion_event ) ;
2022-10-02 23:06:56 +02:00
if ( ( * env ) - > ExceptionCheck ( env ) )
( * env ) - > ExceptionDescribe ( env ) ;
( * env ) - > DeleteLocalRef ( env , motion_event ) ;
2023-10-28 16:52:58 +02:00
return ret ;
2022-10-02 23:06:56 +02:00
}
2023-10-17 21:33:59 +02:00
static void gdk_event_get_widget_relative_position ( GdkEvent * event , GtkWidget * widget , double * x , double * y )
2022-10-02 23:06:56 +02:00
{
2023-10-17 21:33:59 +02:00
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 ) ;
gtk_widget_translate_coordinates ( window , widget , * x - off_x , * y - off_y , x , y ) ;
2022-10-02 23:06:56 +02:00
}
2023-10-17 21:33:59 +02:00
// TODO: find a way to reconcile this with libandroid/input.c?
static gboolean on_event ( GtkEventControllerLegacy * event_controller , GdkEvent * event , struct touch_callback_data * d )
2022-10-02 23:06:56 +02:00
{
2023-10-17 21:33:59 +02:00
double x ;
double y ;
GtkWidget * widget = gtk_event_controller_get_widget ( GTK_EVENT_CONTROLLER ( event_controller ) ) ;
// TODO: this doesn't work for multitouch
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 ) ;
2023-10-28 16:52:58 +02:00
return call_ontouch_callback ( MOTION_EVENT_ACTION_DOWN , x , y , d ) ;
2023-10-17 21:33:59 +02:00
break ;
case GDK_BUTTON_RELEASE :
case GDK_TOUCH_END :
gdk_event_get_widget_relative_position ( event , widget , & x , & y ) ;
2023-10-28 16:52:58 +02:00
return call_ontouch_callback ( MOTION_EVENT_ACTION_UP , x , y , d ) ;
2023-10-17 21:33:59 +02:00
break ;
case GDK_MOTION_NOTIFY :
2023-11-03 22:16:57 +01:00
if ( ! ( gdk_event_get_modifier_state ( event ) & GDK_BUTTON1_MASK ) )
2023-10-17 21:33:59 +02:00
break ;
case GDK_TOUCH_UPDATE :
gdk_event_get_widget_relative_position ( event , widget , & x , & y ) ;
2023-10-28 16:52:58 +02:00
return call_ontouch_callback ( MOTION_EVENT_ACTION_MOVE , x , y , d ) ;
2023-10-17 21:33:59 +02:00
break ;
default :
break ;
}
2023-10-28 16:52:58 +02:00
return false ;
2022-10-02 23:06:56 +02:00
}
static void on_click ( GtkGestureClick * gesture , int n_press , double x , double y , struct touch_callback_data * d )
{
JNIEnv * env ;
( * d - > jvm ) - > GetEnv ( d - > jvm , ( void * * ) & env , JNI_VERSION_1_6 ) ;
( * env ) - > CallBooleanMethod ( env , d - > on_touch_listener , _METHOD ( d - > on_touch_listener_class , " onClick " , " (Landroid/view/View;)V " ) , d - > this ) ;
if ( ( * env ) - > ExceptionCheck ( env ) )
( * env ) - > ExceptionDescribe ( env ) ;
}
2023-10-17 21:33:59 +02:00
void _setOnTouchListener ( JNIEnv * env , jobject this , GtkWidget * widget , jobject on_touch_listener )
2022-10-02 23:06:56 +02:00
{
JavaVM * jvm ;
( * env ) - > GetJavaVM ( env , & jvm ) ;
struct touch_callback_data * callback_data = malloc ( sizeof ( struct touch_callback_data ) ) ;
callback_data - > jvm = jvm ;
callback_data - > this = _REF ( this ) ;
2023-10-17 21:33:59 +02:00
callback_data - > on_touch_listener = on_touch_listener ? _REF ( on_touch_listener ) : NULL ;
callback_data - > on_touch_listener_class = on_touch_listener ? _REF ( _CLASS ( callback_data - > on_touch_listener ) ) : _REF ( _CLASS ( callback_data - > this ) ) ;
2022-10-02 23:06:56 +02:00
2023-10-31 16:00:35 +01:00
GtkEventController * old_controller = g_object_get_data ( G_OBJECT ( widget ) , " on_touch_listener " ) ;
if ( old_controller )
gtk_widget_remove_controller ( widget , old_controller ) ;
2023-10-17 21:33:59 +02:00
GtkEventController * controller = GTK_EVENT_CONTROLLER ( gtk_event_controller_legacy_new ( ) ) ;
2023-10-30 22:35:31 +01:00
gtk_event_controller_set_propagation_phase ( controller , GTK_PHASE_BUBBLE ) ;
2022-10-02 23:06:56 +02:00
2023-10-17 21:33:59 +02:00
g_signal_connect ( controller , " event " , G_CALLBACK ( on_event ) , callback_data ) ;
2022-10-02 23:06:56 +02:00
gtk_widget_add_controller ( widget , controller ) ;
2023-10-31 16:00:35 +01:00
g_object_set_data ( G_OBJECT ( widget ) , " on_touch_listener " , controller ) ;
2022-10-02 23:06:56 +02:00
}
2023-10-17 21:33:59 +02:00
JNIEXPORT void JNICALL Java_android_view_View_setOnTouchListener ( JNIEnv * env , jobject this , jobject on_touch_listener )
{
GtkWidget * widget = GTK_WIDGET ( _PTR ( _GET_LONG_FIELD ( this , " widget " ) ) ) ;
_setOnTouchListener ( env , this , widget , on_touch_listener ) ;
}
2022-10-02 23:06:56 +02:00
JNIEXPORT void JNICALL Java_android_view_View_setOnClickListener ( JNIEnv * env , jobject this , jobject on_click_listener )
{
GtkWidget * widget = GTK_WIDGET ( _PTR ( _GET_LONG_FIELD ( this , " widget " ) ) ) ;
2023-08-23 09:16:45 +02:00
if ( ! on_click_listener )
return ;
2022-10-02 23:06:56 +02:00
JavaVM * jvm ;
( * env ) - > GetJavaVM ( env , & jvm ) ;
struct touch_callback_data * callback_data = malloc ( sizeof ( struct touch_callback_data ) ) ;
callback_data - > jvm = jvm ;
callback_data - > this = _REF ( this ) ;
callback_data - > on_touch_listener = _REF ( on_click_listener ) ;
callback_data - > on_touch_listener_class = _REF ( _CLASS ( callback_data - > on_touch_listener ) ) ;
2023-10-31 16:00:35 +01:00
GtkEventController * old_controller = g_object_get_data ( G_OBJECT ( widget ) , " on_click_listener " ) ;
if ( old_controller )
gtk_widget_remove_controller ( widget , old_controller ) ;
2022-10-02 23:06:56 +02:00
GtkEventController * controller = GTK_EVENT_CONTROLLER ( gtk_gesture_click_new ( ) ) ;
g_signal_connect ( controller , " released " , G_CALLBACK ( on_click ) , callback_data ) ; // the release completes the click, I guess?
gtk_widget_add_controller ( widget , controller ) ;
2023-10-31 16:00:35 +01:00
g_object_set_data ( G_OBJECT ( widget ) , " on_click_listener " , controller ) ;
2022-10-02 23:06:56 +02:00
}
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 ) ;
*/
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 " ) ) ) ;
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)
2023-09-08 18:32:34 +02:00
# define MATCH_PARENT (-1)
2022-10-02 23:06:56 +02:00
2023-09-08 18:32:34 +02:00
JNIEXPORT void JNICALL Java_android_view_View_native_1setLayoutParams ( JNIEnv * env , jobject this , jlong widget_ptr , jint width , jint height , jint gravity , jfloat weight )
{
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 ) {
printf ( " :::-: setting gravity: %d \n " , gravity ) ;
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
}
2022-10-02 23:06:56 +02:00
}
2023-09-08 18:32:34 +02:00
if ( weight > 0.f ) {
printf ( " :::-: setting weight: %f \n " , weight ) ;
2023-09-01 12:42:57 +02:00
2023-09-08 18:32:34 +02:00
hexpand = TRUE ;
vexpand = TRUE ;
}
2022-10-02 23:06:56 +02:00
2023-09-01 12:42:57 +02:00
if ( width = = MATCH_PARENT ) {
2023-09-08 18:32:34 +02:00
hexpand = true ;
halign = GTK_ALIGN_FILL ;
2023-09-01 12:42:57 +02:00
}
if ( height = = MATCH_PARENT ) {
2023-09-08 18:32:34 +02:00
vexpand = true ;
valign = GTK_ALIGN_FILL ;
2023-09-01 12:42:57 +02:00
}
2023-09-08 18:32:34 +02:00
gtk_widget_set_hexpand ( widget , hexpand ) ;
gtk_widget_set_vexpand ( widget , vexpand ) ;
gtk_widget_set_halign ( widget , halign ) ;
gtk_widget_set_valign ( widget , valign ) ;
2022-10-02 23:06:56 +02:00
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 ) ;
2023-10-31 22:56:22 +01:00
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 ) ;
2022-10-02 23:06:56 +02:00
}
2023-11-08 18:11:52 +01:00
JNIEXPORT void JNICALL Java_android_view_View_native_1setVisibility ( JNIEnv * env , jobject this , jlong widget_ptr , jint visibility ) {
GtkWidget * widget = gtk_widget_get_parent ( GTK_WIDGET ( _PTR ( widget_ptr ) ) ) ;
2022-10-02 23:06:56 +02:00
switch ( visibility ) {
case android_view_View_VISIBLE :
gtk_widget_set_visible ( widget , true ) ;
break ;
2023-06-18 11:21:15 +02:00
// TODO: View.INVISIBLE should still reserve layout space for the hidden view
2022-10-02 23:06:56 +02:00
case android_view_View_INVISIBLE :
case android_view_View_GONE :
gtk_widget_set_visible ( widget , false ) ;
break ;
}
}
2023-08-17 12:59:37 +02:00
JNIEXPORT jlong JNICALL Java_android_view_View_native_1constructor ( JNIEnv * env , jobject this , jobject context , jobject attrs )
2022-10-02 23:06:56 +02:00
{
2023-08-22 13:49:09 +02:00
GtkWidget * wrapper = g_object_ref ( wrapper_widget_new ( ) ) ;
2023-10-17 21:33:59 +02:00
// FIXME: we don't really care all that much what this is, since drawing into empty widgets is done by WrapperWidget, but a drawing area is just confusing
GtkWidget * widget = gtk_drawing_area_new ( ) ;
wrapper_widget_set_child ( WRAPPER_WIDGET ( wrapper ) , widget ) ;
2023-07-14 17:53:12 +02:00
wrapper_widget_set_jobject ( WRAPPER_WIDGET ( wrapper ) , env , this ) ;
2022-10-02 23:06:56 +02:00
2023-10-31 23:02:11 +01:00
jclass class = _CLASS ( this ) ;
jmethodID measure_method = _METHOD ( class , " onMeasure " , " (II)V " ) ;
jmethodID layout_method = _METHOD ( class , " onLayout " , " (ZIIII)V " ) ;
if ( measure_method ! = handle_cache . view . onMeasure | | layout_method ! = handle_cache . view . onLayout ) {
gtk_widget_set_layout_manager ( widget , android_layout_new ( _REF ( this ) ) ) ;
( * env ) - > SetBooleanField ( env , this , _FIELD_ID ( class , " haveCustomMeasure " , " Z " ) , true ) ;
}
2023-10-17 21:33:59 +02:00
return _INTPTR ( widget ) ;
2022-10-02 23:06:56 +02:00
}
2023-07-13 22:38:01 +02:00
2023-10-28 08:04:00 +02:00
JNIEXPORT void JNICALL Java_android_view_View_nativeInvalidate ( JNIEnv * env , jclass class , jlong widget_ptr ) {
2023-07-13 22:38:01 +02:00
GtkWidget * widget = GTK_WIDGET ( _PTR ( widget_ptr ) ) ;
2023-10-31 23:14:47 +01:00
wrapper_widget_queue_draw ( WRAPPER_WIDGET ( gtk_widget_get_parent ( widget ) ) ) ;
2023-07-13 22:38:01 +02:00
}
2023-08-22 13:49:09 +02:00
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 ) ) ) ;
}
2023-08-22 14:18:33 +02:00
# define MEASURE_SPEC_UNSPECIFIED (0 << 30)
# define MEASURE_SPEC_EXACTLY (1 << 30)
2023-10-14 11:06:27 +02:00
# define MEASURE_SPEC_AT_MOST (2 << 30)
2023-08-22 14:18:33 +02:00
# define MEASURE_SPEC_MASK (0x3 << 30)
2023-10-14 11:06:27 +02:00
JNIEXPORT void JNICALL Java_android_view_View_native_1measure ( JNIEnv * env , jobject this , jlong widget_ptr , jint width_spec , jint height_spec , jboolean is_complex ) {
2023-08-22 14:18:33 +02:00
int width ;
int height ;
int for_size ;
GtkWidget * widget = gtk_widget_get_parent ( GTK_WIDGET ( _PTR ( widget_ptr ) ) ) ;
2023-10-14 11:06:27 +02:00
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 ;
2023-08-22 14:18:33 +02:00
2023-10-14 11:06:27 +02:00
if ( width_spec_mode = = MEASURE_SPEC_EXACTLY | | ( ! is_complex & & width_spec_mode = = MEASURE_SPEC_AT_MOST ) ) {
width = width_spec_size ;
2023-08-22 14:18:33 +02:00
} else {
2023-10-14 11:06:27 +02:00
for_size = ( height_spec_mode = = MEASURE_SPEC_EXACTLY ) ? height_spec_size : - 1 ;
2023-08-22 14:18:33 +02:00
gtk_widget_measure ( widget , GTK_ORIENTATION_HORIZONTAL , for_size , NULL , & width , NULL , NULL ) ;
2023-09-01 12:42:57 +02:00
}
2023-10-14 11:06:27 +02:00
if ( width_spec_mode = = MEASURE_SPEC_AT_MOST & & width > width_spec_size ) {
width = width_spec_size ;
}
2023-08-22 14:18:33 +02:00
2023-10-14 11:06:27 +02:00
if ( height_spec_mode = = MEASURE_SPEC_EXACTLY | | ( ! is_complex & & height_spec_mode = = MEASURE_SPEC_AT_MOST ) ) {
height = height_spec_size ;
2023-09-01 12:42:57 +02:00
} else {
2023-10-14 11:06:27 +02:00
for_size = ( width_spec_mode = = MEASURE_SPEC_EXACTLY ) ? width_spec_size : - 1 ;
2023-08-22 14:18:33 +02:00
gtk_widget_measure ( widget , GTK_ORIENTATION_VERTICAL , for_size , NULL , & height , NULL , NULL ) ;
}
2023-10-14 11:06:27 +02:00
if ( height_spec_mode = = MEASURE_SPEC_AT_MOST & & height > height_spec_size ) {
height = height_spec_size ;
}
2023-09-01 12:42:57 +02:00
2023-08-22 14:18:33 +02:00
( * 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 ,
. width = r - l ,
. height = b - t ,
} ;
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 ) ;
}
2023-10-30 17:28:38 +01:00
// FIXME: this will probably behave unfortunately if called multiple times
JNIEXPORT void JNICALL Java_android_view_View_setBackgroundColor ( JNIEnv * env , jobject this , jint color )
{
GtkWidget * widget = GTK_WIDGET ( _PTR ( _GET_LONG_FIELD ( this , " widget " ) ) ) ;
GtkCssProvider * css_provider = gtk_css_provider_new ( ) ;
char * css_string = g_markup_printf_escaped ( " * { background-color: #%06x%02x; } " , color & 0xFFFFFF , ( color > > 24 ) & 0xFF ) ;
gtk_css_provider_load_from_string ( css_provider , css_string ) ;
g_free ( css_string ) ;
gtk_style_context_add_provider ( gtk_widget_get_style_context ( gtk_widget_get_parent ( widget ) ) , GTK_STYLE_PROVIDER ( css_provider ) , GTK_STYLE_PROVIDER_PRIORITY_APPLICATION ) ;
}