diff --git a/src/api-impl-jni/generated_headers/android_view_View.h b/src/api-impl-jni/generated_headers/android_view_View.h index 02aaf7ed..7ab56fd9 100644 --- a/src/api-impl-jni/generated_headers/android_view_View.h +++ b/src/api-impl-jni/generated_headers/android_view_View.h @@ -263,6 +263,30 @@ JNIEXPORT void JNICALL Java_android_view_View_native_1set_1size_1request JNIEXPORT void JNICALL Java_android_view_View_native_1destructor (JNIEnv *, jobject, jlong); +/* + * Class: android_view_View + * Method: native_measure + * Signature: (JII)V + */ +JNIEXPORT void JNICALL Java_android_view_View_native_1measure + (JNIEnv *, jobject, jlong, jint, jint); + +/* + * Class: android_view_View + * Method: native_layout + * Signature: (JIIII)V + */ +JNIEXPORT void JNICALL Java_android_view_View_native_1layout + (JNIEnv *, jobject, jlong, jint, jint, jint, jint); + +/* + * Class: android_view_View + * Method: native_requestLayout + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_android_view_View_native_1requestLayout + (JNIEnv *, jobject, jlong); + /* * Class: android_view_View * Method: nativeInvalidate diff --git a/src/api-impl-jni/util.c b/src/api-impl-jni/util.c index 4bad3088..c0e0ca2d 100644 --- a/src/api-impl-jni/util.c +++ b/src/api-impl-jni/util.c @@ -109,6 +109,12 @@ void set_up_handle_cache(JNIEnv *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.view.onLayout = _METHOD(handle_cache.view.class, "onLayout", "(ZIIII)V"); + handle_cache.view.getMeasuredWidth = _METHOD(handle_cache.view.class, "getMeasuredWidth", "()I"); + handle_cache.view.getMeasuredHeight = _METHOD(handle_cache.view.class, "getMeasuredHeight", "()I"); + handle_cache.view.getSuggestedMinimumWidth = _METHOD(handle_cache.view.class, "getSuggestedMinimumWidth", "()I"); + handle_cache.view.getSuggestedMinimumHeight = _METHOD(handle_cache.view.class, "getSuggestedMinimumHeight", "()I"); + handle_cache.view.setMeasuredDimension = _METHOD(handle_cache.view.class, "setMeasuredDimension", "(II)V"); 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 d2a39948..5cc0fc06 100644 --- a/src/api-impl-jni/util.h +++ b/src/api-impl-jni/util.h @@ -70,6 +70,12 @@ struct handle_cache { jmethodID setLayoutParams; jmethodID onDraw; jmethodID onMeasure; + jmethodID onLayout; + jmethodID getMeasuredWidth; + jmethodID getMeasuredHeight; + jmethodID getSuggestedMinimumWidth; + jmethodID getSuggestedMinimumHeight; + jmethodID setMeasuredDimension; } view; struct { jclass class; diff --git a/src/api-impl-jni/views/android_view_View.c b/src/api-impl-jni/views/android_view_View.c index 9d558430..5f42dacd 100644 --- a/src/api-impl-jni/views/android_view_View.c +++ b/src/api-impl-jni/views/android_view_View.c @@ -196,3 +196,44 @@ JNIEXPORT void JNICALL Java_android_view_View_native_1destructor(JNIEnv *env, jo { 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_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; + int height; + int for_size; + GtkWidget *widget = gtk_widget_get_parent(GTK_WIDGET(_PTR(widget_ptr))); + + if (((height_spec & MEASURE_SPEC_MASK) == MEASURE_SPEC_EXACTLY) && ((width_spec & MEASURE_SPEC_MASK) == MEASURE_SPEC_EXACTLY)) { + width = width_spec & ~MEASURE_SPEC_MASK; + height = height_spec & ~MEASURE_SPEC_MASK; + } else { + for_size = ((height_spec & MEASURE_SPEC_MASK) == MEASURE_SPEC_EXACTLY) ? (height_spec & ~MEASURE_SPEC_MASK) : -1; + gtk_widget_measure(widget, GTK_ORIENTATION_HORIZONTAL, for_size, NULL, &width, NULL, NULL); + + for_size = ((width_spec & MEASURE_SPEC_MASK) == MEASURE_SPEC_EXACTLY) ? (width_spec & ~MEASURE_SPEC_MASK) : -1; + gtk_widget_measure(widget, GTK_ORIENTATION_VERTICAL, for_size, NULL, &height, NULL, NULL); + } + (*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); +} diff --git a/src/api-impl-jni/views/android_view_ViewGroup.c b/src/api-impl-jni/views/android_view_ViewGroup.c index 6dd7b395..fec61320 100644 --- a/src/api-impl-jni/views/android_view_ViewGroup.c +++ b/src/api-impl-jni/views/android_view_ViewGroup.c @@ -8,6 +8,62 @@ #include "../generated_headers/android_view_ViewGroup.h" #include "../generated_headers/android_view_View.h" +#define MEASURE_SPEC_EXACTLY (1 << 30) + +struct _AndroidLayout { + GtkLayoutManager parent_instance; + jobject view; +}; +G_DECLARE_FINAL_TYPE(AndroidLayout, android_layout, ATL, ANDROID_LAYOUT, GtkLayoutManager); + +static void android_layout_measure(GtkLayoutManager *layout_manager, GtkWidget *widget, GtkOrientation orientation, int for_size, int *minimum, int *natural, int *minimum_baseline, int *natural_baseline) { + AndroidLayout *layout = ATL_ANDROID_LAYOUT(layout_manager); + JNIEnv *env = get_jni_env(); + + if (orientation == GTK_ORIENTATION_HORIZONTAL) { + *minimum = (*env)->CallIntMethod(env, layout->view, handle_cache.view.getSuggestedMinimumWidth); + *natural = (*env)->CallIntMethod(env, layout->view, handle_cache.view.getMeasuredWidth); + } + if (orientation == GTK_ORIENTATION_VERTICAL) { + *minimum = (*env)->CallIntMethod(env, layout->view, handle_cache.view.getSuggestedMinimumHeight); + *natural = (*env)->CallIntMethod(env, layout->view, handle_cache.view.getMeasuredHeight); + } + if (*natural < *minimum) + *natural = *minimum; + + *minimum_baseline = -1; + *natural_baseline = -1; +} + +static void android_layout_allocate(GtkLayoutManager *layout_manager, GtkWidget *widget, int width, int height, int baseline) { + AndroidLayout *layout = ATL_ANDROID_LAYOUT(layout_manager); + JNIEnv *env = get_jni_env(); + + (*env)->CallVoidMethod(env, layout->view, handle_cache.view.onMeasure, MEASURE_SPEC_EXACTLY | width, MEASURE_SPEC_EXACTLY | height); + if((*env)->ExceptionCheck(env)) + (*env)->ExceptionDescribe(env); + + (*env)->CallVoidMethod(env, layout->view, handle_cache.view.onLayout, TRUE, 0, 0, width, height); + if((*env)->ExceptionCheck(env)) + (*env)->ExceptionDescribe(env); +} + +static void android_layout_class_init(AndroidLayoutClass *klass) { + klass->parent_class.measure = android_layout_measure; + klass->parent_class.allocate = android_layout_allocate; +} + +static void android_layout_init(AndroidLayout *self) { +} + +G_DEFINE_TYPE(AndroidLayout, android_layout, GTK_TYPE_LAYOUT_MANAGER) + +static GtkLayoutManager *android_layout_new(jobject view) { + AndroidLayout *layout = g_object_new(android_layout_get_type(), NULL); + layout->view = view; + return &layout->parent_instance; +} + /** * Should be overwritten by ViewGroup subclasses. * Fall back to vertical GtkBox if subclass is not implemented yet @@ -17,7 +73,17 @@ JNIEXPORT jlong JNICALL Java_android_view_ViewGroup_native_1constructor(JNIEnv * GtkWidget *wrapper = g_object_ref(wrapper_widget_new()); GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 1); // spacing of 1 wrapper_widget_set_child(WRAPPER_WIDGET(wrapper), box); - gtk_widget_set_name(GTK_WIDGET(box), "ViewGroup"); + + const char *name = _CSTRING((*env)->CallObjectMethod(env, _CLASS(this), + _METHOD((*env)->FindClass(env, "java/lang/Class"), "getName", "()Ljava/lang/String;"))); + gtk_widget_set_name(box, name); + + jmethodID measure_method = _METHOD(_CLASS(this), "onMeasure", "(II)V"); + jmethodID layout_method = _METHOD(_CLASS(this), "onLayout", "(ZIIII)V"); + if (measure_method != handle_cache.view.onMeasure || layout_method != handle_cache.view.onLayout) { + gtk_widget_set_layout_manager(box, android_layout_new(_REF(this))); + } + return _INTPTR(box); } diff --git a/src/api-impl-jni/widgets/WrapperWidget.c b/src/api-impl-jni/widgets/WrapperWidget.c index d47bedca..775937b4 100644 --- a/src/api-impl-jni/widgets/WrapperWidget.c +++ b/src/api-impl-jni/widgets/WrapperWidget.c @@ -146,7 +146,13 @@ static void on_mapped(GtkWidget* self, gpointer data) JNIEnv *env; (*wrapper->jvm)->GetEnv(wrapper->jvm, (void**)&env, JNI_VERSION_1_6); - (*env)->CallVoidMethod(env, wrapper->jobj, wrapper->measure_method, MEASURE_SPEC_EXACTLY, MEASURE_SPEC_EXACTLY); + (*env)->CallVoidMethod(env, wrapper->jobj, wrapper->measure_method, MEASURE_SPEC_EXACTLY | gtk_widget_get_width(self), MEASURE_SPEC_EXACTLY | gtk_widget_get_height(self)); + int width = (*env)->CallIntMethod(env, wrapper->jobj, handle_cache.view.getMeasuredWidth); + if (width > 0) + g_object_set(G_OBJECT(self), "width-request", width, NULL); + int height = (*env)->CallIntMethod(env, wrapper->jobj, handle_cache.view.getMeasuredHeight); + if (height > 0) + g_object_set(G_OBJECT(self), "height-request", height, NULL); } } diff --git a/src/api-impl/android/view/View.java b/src/api-impl/android/view/View.java index 1369961d..4b91f661 100644 --- a/src/api-impl/android/view/View.java +++ b/src/api-impl/android/view/View.java @@ -771,6 +771,14 @@ public class View extends Object { private Context context; private Map tags = new HashMap<>(); + int measuredWidth = 0; + int measuredHeight = 0; + + private int left; + private int top; + private int right; + private int bottom; + public long widget; // pointer public static HashMap view_by_id = new HashMap(); @@ -829,7 +837,8 @@ public class View extends Object { } protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { - native_set_size_request(MeasureSpec.getSize(measuredWidth), MeasureSpec.getSize(measuredHeight)); + this.measuredWidth = measuredWidth; + this.measuredHeight = measuredHeight; } public Resources getResources() { @@ -846,6 +855,9 @@ public class View extends Object { protected native long native_constructor(Context context, AttributeSet attrs); // will create a custom GtkWidget with a custom drawing function private native void native_set_size_request(int width, int height); protected native void native_destructor(long widget); + protected native void native_measure(long widget, int widthMeasureSpec, int heightMeasureSpec); + protected native void native_layout(long widget, int l, int t, int r, int b); + protected native void native_requestLayout(long widget); // --- stubs @@ -879,7 +891,9 @@ public class View extends Object { return system_ui_visibility; }; - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {} + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + native_measure(widget, widthMeasureSpec, heightMeasureSpec); + } public void setPressed(boolean pressed) { System.out.println("calling setPressed on " + this + " with value: " + pressed); @@ -994,14 +1008,93 @@ public class View extends Object { public void setOnHoverListener(OnHoverListener listener) {} - public final void measure (int widthMeasureSpec, int heightMeasureSpec) {} + public final void measure(int widthMeasureSpec, int heightMeasureSpec) { + onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + public final int getMeasuredState() { + return 0; + } + + public static int combineMeasuredStates(int curState, int newState) { + return curState | newState; + } + + protected int getSuggestedMinimumHeight() { + return 50; + } + protected int getSuggestedMinimumWidth() { + return 100; + } + + /** + * Utility to reconcile a desired size and state, with constraints imposed + * by a MeasureSpec. Will take the desired size, unless a different size + * is imposed by the constraints. The returned value is a compound integer, + * with the resolved size in the {@link #MEASURED_SIZE_MASK} bits and + * optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the resulting + * size is smaller than the size the view wants to be. + * + * @param size How big the view wants to be + * @param measureSpec Constraints imposed by the parent + * @return Size information bit mask as defined by + * {@link #MEASURED_SIZE_MASK} and {@link #MEASURED_STATE_TOO_SMALL}. + */ + public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) { + int result = size; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + switch (specMode) { + case MeasureSpec.UNSPECIFIED: + result = size; + break; + case MeasureSpec.AT_MOST: + if (specSize < size) { + result = specSize | MEASURED_STATE_TOO_SMALL; + } else { + result = size; + } + break; + case MeasureSpec.EXACTLY: + result = specSize; + break; + } + return result | (childMeasuredState&MEASURED_STATE_MASK); + } public final int getMeasuredWidth() { - return getWidth(); + return this.measuredWidth & MEASURED_SIZE_MASK; } public final int getMeasuredHeight() { - return getHeight(); + return this.measuredHeight & MEASURED_SIZE_MASK; + } + + protected void onLayout(boolean changed, int l, int t, int r, int b) {} + + public void layout(int l, int t, int r, int b) { + this.left = l; + this.top = t; + this.right = r; + this.bottom = b; + native_layout(widget, l, t, r, b); + } + + public int getLeft() { + return left; + } + public int getTop() { + return top; + } + public int getRight() { + return right; + } + public int getBottom() { + return bottom; + } + + public void offsetTopAndBottom(int offset) { + layout(left, top + offset, right, bottom + offset); } public void setBackgroundDrawable(Drawable backgroundDrawable) {} @@ -1016,7 +1109,9 @@ public class View extends Object { public boolean removeCallbacks(Runnable action) {return false;} - public void requestLayout() {}; + public void requestLayout() { + native_requestLayout(widget); + }; public void setOverScrollMode(int mode) {} @@ -1024,12 +1119,12 @@ public class View extends Object { public boolean postDelayed(Runnable action, long delayMillis) { new Handler(Looper.getMainLooper()).postDelayed(action, delayMillis); - return true; - } + return true; + } public boolean post(Runnable action) { new Handler(Looper.getMainLooper()).post(action); - return true; + return true; } public void setSaveFromParentEnabled(boolean enabled) {} diff --git a/src/api-impl/android/view/ViewGroup.java b/src/api-impl/android/view/ViewGroup.java index 40cd8e7e..758e71dd 100644 --- a/src/api-impl/android/view/ViewGroup.java +++ b/src/api-impl/android/view/ViewGroup.java @@ -183,6 +183,19 @@ public class ViewGroup extends View implements ViewParent, ViewManager { return MeasureSpec.makeMeasureSpec(resultSize, resultMode); } + protected void measureChildWithMargins(View child, + int parentWidthMeasureSpec, int widthUsed, + int parentHeightMeasureSpec, int heightUsed) { + final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); + final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, + /*mPaddingLeft + mPaddingRight +*/ lp.leftMargin + lp.rightMargin + + widthUsed, lp.width); + final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, + /*mPaddingTop + mPaddingBottom +*/ lp.topMargin + lp.bottomMargin + + heightUsed, lp.height); + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + } + public static class LayoutParams { public static final int FILL_PARENT = -1; public static final int MATCH_PARENT = -1;