/* * el_android.c — Android JNI backend for the el native widget system. * * This file implements the Android widget layer that el_seed.c calls through * to when EL_TARGET_ANDROID is defined. It is the exact Android counterpart * to el_appkit.m and presents the same C API surface. * * Architecture: * el program (el code) * → __widget_* C builtins in el_seed.c * → el_android_* C-callable functions declared here * → ElBridge static methods in Java via JNI * → android.view.View subclasses on the UI thread * * Widget handles: every widget (window root, view, control) is assigned an * int64_t slot index into view_slots[]. The el program holds these as opaque * Int values. Slot 0 is never valid (null handle = -1 convention). * * Threading: Android requires all UI operations to run on the main (UI) thread. * Every JNI call that mutates a View is dispatched through * Activity.runOnUiThread(Runnable) if the current thread is not the UI thread. * el_android_run_loop is a no-op — Android lifecycle is driven by the Activity. * * Callback mechanism: when a widget fires an event Java calls * nativeOnClick / nativeOnChange / nativeOnSubmit * The C side looks up the registered El function name for that slot, then: * dlsym(RTLD_DEFAULT, fn_name)(widget_handle, event_data_string) * This matches the __thread_create pattern in el_seed.c exactly. * * Compile / link (as part of libelruntime.so): * Compiled by the Android Gradle NDK build system with -DEL_TARGET_ANDROID. * Link flags: -landroid -llog -ldl * * Java companion: ElBridge.java in the same directory must be compiled into * the Android application's APK (package com.neuron.el). */ #ifdef EL_TARGET_ANDROID #include #include #include #include #include #include #include "el_runtime.h" /* ── Logging ─────────────────────────────────────────────────────────────── */ #define EL_TAG "ElAndroid" #define EL_LOGI(...) __android_log_print(ANDROID_LOG_INFO, EL_TAG, __VA_ARGS__) #define EL_LOGW(...) __android_log_print(ANDROID_LOG_WARN, EL_TAG, __VA_ARGS__) #define EL_LOGE(...) __android_log_print(ANDROID_LOG_ERROR, EL_TAG, __VA_ARGS__) /* ── JNI global state ────────────────────────────────────────────────────── */ static JavaVM *g_jvm = NULL; static jobject g_activity = NULL; /* global ref to Activity */ static jclass g_bridge_class = NULL; /* global ref to ElBridge class */ /* Cached method IDs on ElBridge — filled in el_android_init(). */ static jmethodID g_mid_createLinearLayout = NULL; static jmethodID g_mid_createFrameLayout = NULL; static jmethodID g_mid_createScrollView = NULL; static jmethodID g_mid_createTextView = NULL; static jmethodID g_mid_createButton = NULL; static jmethodID g_mid_createEditText = NULL; static jmethodID g_mid_createImageView = NULL; static jmethodID g_mid_setContentView = NULL; static jmethodID g_mid_setTitle = NULL; static jmethodID g_mid_addChild = NULL; static jmethodID g_mid_removeChild = NULL; static jmethodID g_mid_destroyView = NULL; static jmethodID g_mid_setText = NULL; static jmethodID g_mid_getText = NULL; static jmethodID g_mid_setTextColor = NULL; static jmethodID g_mid_setBackgroundColor = NULL; static jmethodID g_mid_setFont = NULL; static jmethodID g_mid_setPadding = NULL; static jmethodID g_mid_setWidth = NULL; static jmethodID g_mid_setHeight = NULL; static jmethodID g_mid_setFlex = NULL; static jmethodID g_mid_setCornerRadius = NULL; static jmethodID g_mid_setEnabled = NULL; static jmethodID g_mid_setVisibility = NULL; static jmethodID g_mid_setOnClickListener = NULL; static jmethodID g_mid_setOnChangeListener = NULL; static jmethodID g_mid_setOnSubmitListener = NULL; static jmethodID g_mid_runOnUiThread = NULL; /* Activity.runOnUiThread */ /* ── Widget table ─────────────────────────────────────────────────────────── */ #define EL_ANDROID_MAX_WIDGETS 4096 typedef enum { EL_WIDGET_FREE = 0, EL_WIDGET_WINDOW = 1, EL_WIDGET_VSTACK = 2, EL_WIDGET_HSTACK = 3, EL_WIDGET_ZSTACK = 4, EL_WIDGET_SCROLL = 5, EL_WIDGET_LABEL = 6, EL_WIDGET_BUTTON = 7, EL_WIDGET_TEXTFIELD = 8, EL_WIDGET_TEXTAREA = 9, EL_WIDGET_IMAGE = 10, } ElWidgetKind; typedef struct { ElWidgetKind kind; jint slot; /* Java-side slot index (matches C index) */ char *cb_click; /* El function name for click / submit events */ char *cb_change; /* El function name for value-change events */ } ElWidget; static ElWidget _el_widgets[EL_ANDROID_MAX_WIDGETS]; static int64_t el_widget_alloc(ElWidgetKind kind, jint slot) { for (int i = 1; i < EL_ANDROID_MAX_WIDGETS; i++) { if (_el_widgets[i].kind == EL_WIDGET_FREE) { _el_widgets[i].kind = kind; _el_widgets[i].slot = slot; _el_widgets[i].cb_click = NULL; _el_widgets[i].cb_change = NULL; return (int64_t)i; } } EL_LOGE("el_widget_alloc: slot table full"); return -1; } static ElWidget *el_widget_get(int64_t handle) { if (handle <= 0 || handle >= EL_ANDROID_MAX_WIDGETS) return NULL; if (_el_widgets[handle].kind == EL_WIDGET_FREE) return NULL; return &_el_widgets[handle]; } static void el_widget_free(int64_t handle) { ElWidget *w = el_widget_get(handle); if (!w) return; w->kind = EL_WIDGET_FREE; w->slot = -1; free(w->cb_click); w->cb_click = NULL; free(w->cb_change); w->cb_change = NULL; } /* ── JNI environment helpers ─────────────────────────────────────────────── */ /* * Obtain a JNIEnv for the calling thread. Attaches the thread to the JVM if * needed (detaches in el_jni_detach_if_attached — call in pairs). */ static int g_was_attached = 0; /* thread-local would be cleaner but this is safe for single-threaded el programs */ static JNIEnv *el_jni_env(void) { if (!g_jvm) return NULL; JNIEnv *env = NULL; jint rc = (*g_jvm)->GetEnv(g_jvm, (void **)&env, JNI_VERSION_1_6); if (rc == JNI_OK) { g_was_attached = 0; return env; } if (rc == JNI_EDETACHED) { if ((*g_jvm)->AttachCurrentThread(g_jvm, &env, NULL) == JNI_OK) { g_was_attached = 1; return env; } } EL_LOGE("el_jni_env: failed to obtain JNIEnv"); return NULL; } static void el_jni_detach_if_attached(void) { if (g_was_attached && g_jvm) { (*g_jvm)->DetachCurrentThread(g_jvm); g_was_attached = 0; } } /* ── UI-thread dispatch ──────────────────────────────────────────────────── */ /* * Most ElBridge static methods already dispatch to the UI thread internally * (they call Activity.runOnUiThread). The helper below is available for * cases where the caller needs to be sure the call has completed before * returning (ElBridge methods marked "sync" use a CountDownLatch internally). * * For the current implementation we call ElBridge methods directly; ElBridge * itself marshals to the UI thread via Activity.runOnUiThread + latch. * This keeps the C side simple and mirrors the AppKit dispatch_sync pattern. */ /* ── JNI_OnLoad ──────────────────────────────────────────────────────────── */ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { (void)reserved; g_jvm = vm; EL_LOGI("JNI_OnLoad: el Android bridge loaded"); return JNI_VERSION_1_6; } /* ── el_android_init ─────────────────────────────────────────────────────── */ /* * Called from __native_init(). The Activity must have already called * ElBridge.registerActivity(activity) from Java before this runs, which sets * g_activity via the nativeRegisterActivity JNI method below. * * Caches all method IDs used later so individual widget calls avoid repeated * FindClass / GetStaticMethodID lookups. */ void el_android_init(void) { static int done = 0; if (done) return; done = 1; JNIEnv *env = el_jni_env(); if (!env) { EL_LOGE("el_android_init: no JNIEnv"); return; } jclass cls = (*env)->FindClass(env, "com/neuron/el/ElBridge"); if (!cls) { EL_LOGE("el_android_init: ElBridge class not found"); return; } g_bridge_class = (*env)->NewGlobalRef(env, cls); (*env)->DeleteLocalRef(env, cls); #define CACHE_STATIC(var, name, sig) \ var = (*env)->GetStaticMethodID(env, g_bridge_class, name, sig); \ if (!var) EL_LOGW("el_android_init: method not found: %s %s", name, sig) CACHE_STATIC(g_mid_createLinearLayout, "createLinearLayout", "(II)I"); CACHE_STATIC(g_mid_createFrameLayout, "createFrameLayout", "()I"); CACHE_STATIC(g_mid_createScrollView, "createScrollView", "()I"); CACHE_STATIC(g_mid_createTextView, "createTextView", "(Ljava/lang/String;)I"); CACHE_STATIC(g_mid_createButton, "createButton", "(Ljava/lang/String;)I"); CACHE_STATIC(g_mid_createEditText, "createEditText", "(Ljava/lang/String;Z)I"); CACHE_STATIC(g_mid_createImageView, "createImageView", "(Ljava/lang/String;)I"); CACHE_STATIC(g_mid_setContentView, "setContentView", "(I)V"); CACHE_STATIC(g_mid_setTitle, "setTitle", "(Ljava/lang/String;)V"); CACHE_STATIC(g_mid_addChild, "addChild", "(II)V"); CACHE_STATIC(g_mid_removeChild, "removeChild", "(II)V"); CACHE_STATIC(g_mid_destroyView, "destroyView", "(I)V"); CACHE_STATIC(g_mid_setText, "setText", "(ILjava/lang/String;)V"); CACHE_STATIC(g_mid_getText, "getText", "(I)Ljava/lang/String;"); CACHE_STATIC(g_mid_setTextColor, "setTextColor", "(IFFFF)V"); CACHE_STATIC(g_mid_setBackgroundColor, "setBackgroundColor", "(IFFFF)V"); CACHE_STATIC(g_mid_setFont, "setFont", "(ILjava/lang/String;IZ)V"); CACHE_STATIC(g_mid_setPadding, "setPadding", "(IIIII)V"); CACHE_STATIC(g_mid_setWidth, "setWidth", "(II)V"); CACHE_STATIC(g_mid_setHeight, "setHeight", "(II)V"); CACHE_STATIC(g_mid_setFlex, "setFlex", "(II)V"); CACHE_STATIC(g_mid_setCornerRadius, "setCornerRadius", "(IF)V"); CACHE_STATIC(g_mid_setEnabled, "setEnabled", "(IZ)V"); CACHE_STATIC(g_mid_setVisibility, "setVisibility", "(IZ)V"); CACHE_STATIC(g_mid_setOnClickListener, "setOnClickListener", "(I)V"); CACHE_STATIC(g_mid_setOnChangeListener, "setOnChangeListener", "(I)V"); CACHE_STATIC(g_mid_setOnSubmitListener, "setOnSubmitListener", "(I)V"); #undef CACHE_STATIC el_jni_detach_if_attached(); EL_LOGI("el_android_init: complete"); } /* ── JNI: Activity registration ─────────────────────────────────────────── */ /* * Called from Java: ElBridge.registerActivity(activity) calls back here. * Stores a global reference to the Activity so C code can dispatch to it. */ JNIEXPORT void JNICALL Java_com_neuron_el_ElBridge_nativeRegisterActivity(JNIEnv *env, jclass cls, jobject activity) { (void)cls; if (g_activity) { (*env)->DeleteGlobalRef(env, g_activity); g_activity = NULL; } if (activity) { g_activity = (*env)->NewGlobalRef(env, activity); EL_LOGI("nativeRegisterActivity: activity registered"); } } /* ── El callback invocation ──────────────────────────────────────────────── */ /* * Invoke an El callback by symbol name. * Signature matches AppKit: fn(handle: Int, data: String) -> Void * compiled to: void fn(el_val_t handle, el_val_t data) */ typedef void (*ElCb2)(int64_t handle, int64_t data); static void el_android_invoke_cb(const char *fn_name, int64_t handle, const char *data) { if (!fn_name || !*fn_name) return; void *sym = dlsym(RTLD_DEFAULT, fn_name); if (!sym) { EL_LOGW("invoke_cb: symbol not found: %s", fn_name); return; } ElCb2 fn = (ElCb2)sym; fn(handle, (int64_t)(uintptr_t)(data ? data : "")); } /* ── JNI: callbacks from Java → C ───────────────────────────────────────── */ JNIEXPORT void JNICALL Java_com_neuron_el_ElBridge_nativeOnClick(JNIEnv *env, jclass cls, jint slot) { (void)env; (void)cls; int64_t handle = (int64_t)slot; ElWidget *w = el_widget_get(handle); if (w && w->cb_click) { el_android_invoke_cb(w->cb_click, handle, ""); } } JNIEXPORT void JNICALL Java_com_neuron_el_ElBridge_nativeOnChange(JNIEnv *env, jclass cls, jint slot, jstring text) { (void)cls; int64_t handle = (int64_t)slot; ElWidget *w = el_widget_get(handle); if (w && w->cb_change) { const char *ctext = text ? (*env)->GetStringUTFChars(env, text, NULL) : ""; el_android_invoke_cb(w->cb_change, handle, ctext); if (text) (*env)->ReleaseStringUTFChars(env, text, ctext); } } JNIEXPORT void JNICALL Java_com_neuron_el_ElBridge_nativeOnSubmit(JNIEnv *env, jclass cls, jint slot, jstring text) { (void)cls; int64_t handle = (int64_t)slot; ElWidget *w = el_widget_get(handle); if (w && w->cb_click) { /* submit stored in cb_click, same as AppKit */ const char *ctext = text ? (*env)->GetStringUTFChars(env, text, NULL) : ""; el_android_invoke_cb(w->cb_click, handle, ctext); if (text) (*env)->ReleaseStringUTFChars(env, text, ctext); } } /* ── Helper: jstring from C string ──────────────────────────────────────── */ static jstring el_jstr(JNIEnv *env, const char *s) { return (*env)->NewStringUTF(env, s ? s : ""); } /* ── Window ──────────────────────────────────────────────────────────────── */ /* * el_android_window_create — on Android a "window" is the root LinearLayout * set as the Activity's content view. We create a vertical LinearLayout and * store it. el_android_window_show calls setContentView on the Activity. */ int64_t el_android_window_create(const char *title, int width, int height, int min_width, int min_height) { (void)width; (void)height; (void)min_width; (void)min_height; JNIEnv *env = el_jni_env(); if (!env || !g_bridge_class) return -1; /* VERTICAL LinearLayout with no spacing (spacing added via margins in Java) */ jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class, g_mid_createLinearLayout, (jint)1 /* VERTICAL */, (jint)0); if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; } /* Set activity title */ if (g_mid_setTitle && title) { jstring jtitle = el_jstr(env, title); (*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setTitle, jtitle); (*env)->DeleteLocalRef(env, jtitle); } int64_t handle = el_widget_alloc(EL_WIDGET_WINDOW, (int)slot); el_jni_detach_if_attached(); return handle; } void el_android_window_show(int64_t handle) { ElWidget *w = el_widget_get(handle); if (!w || w->kind != EL_WIDGET_WINDOW) return; JNIEnv *env = el_jni_env(); if (!env || !g_bridge_class) return; (*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setContentView, (jint)w->slot); if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env); el_jni_detach_if_attached(); } void el_android_window_set_title(int64_t handle, const char *title) { (void)handle; JNIEnv *env = el_jni_env(); if (!env || !g_bridge_class) return; jstring jtitle = el_jstr(env, title); (*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setTitle, jtitle); (*env)->DeleteLocalRef(env, jtitle); if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env); el_jni_detach_if_attached(); } /* ── Layout containers ───────────────────────────────────────────────────── */ int64_t el_android_vstack_create(int spacing) { JNIEnv *env = el_jni_env(); if (!env || !g_bridge_class) return -1; jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class, g_mid_createLinearLayout, (jint)1 /* VERTICAL */, (jint)spacing); if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; } int64_t h = el_widget_alloc(EL_WIDGET_VSTACK, (int)slot); el_jni_detach_if_attached(); return h; } int64_t el_android_hstack_create(int spacing) { JNIEnv *env = el_jni_env(); if (!env || !g_bridge_class) return -1; jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class, g_mid_createLinearLayout, (jint)0 /* HORIZONTAL */, (jint)spacing); if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; } int64_t h = el_widget_alloc(EL_WIDGET_HSTACK, (int)slot); el_jni_detach_if_attached(); return h; } int64_t el_android_zstack_create(void) { JNIEnv *env = el_jni_env(); if (!env || !g_bridge_class) return -1; jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class, g_mid_createFrameLayout); if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; } int64_t h = el_widget_alloc(EL_WIDGET_ZSTACK, (int)slot); el_jni_detach_if_attached(); return h; } int64_t el_android_scroll_create(void) { JNIEnv *env = el_jni_env(); if (!env || !g_bridge_class) return -1; jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class, g_mid_createScrollView); if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; } int64_t h = el_widget_alloc(EL_WIDGET_SCROLL, (int)slot); el_jni_detach_if_attached(); return h; } /* ── Widget factories ─────────────────────────────────────────────────────── */ int64_t el_android_label_create(const char *text) { JNIEnv *env = el_jni_env(); if (!env || !g_bridge_class) return -1; jstring jt = el_jstr(env, text); jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class, g_mid_createTextView, jt); (*env)->DeleteLocalRef(env, jt); if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; } int64_t h = el_widget_alloc(EL_WIDGET_LABEL, (int)slot); el_jni_detach_if_attached(); return h; } int64_t el_android_button_create(const char *label) { JNIEnv *env = el_jni_env(); if (!env || !g_bridge_class) return -1; jstring jl = el_jstr(env, label); jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class, g_mid_createButton, jl); (*env)->DeleteLocalRef(env, jl); if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; } int64_t h = el_widget_alloc(EL_WIDGET_BUTTON, (int)slot); el_jni_detach_if_attached(); return h; } int64_t el_android_text_field_create(const char *placeholder) { JNIEnv *env = el_jni_env(); if (!env || !g_bridge_class) return -1; jstring jp = el_jstr(env, placeholder); /* singleLine = true */ jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class, g_mid_createEditText, jp, (jboolean)JNI_TRUE); (*env)->DeleteLocalRef(env, jp); if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; } int64_t h = el_widget_alloc(EL_WIDGET_TEXTFIELD, (int)slot); el_jni_detach_if_attached(); return h; } int64_t el_android_text_area_create(const char *placeholder) { JNIEnv *env = el_jni_env(); if (!env || !g_bridge_class) return -1; jstring jp = el_jstr(env, placeholder); /* singleLine = false → multiline EditText */ jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class, g_mid_createEditText, jp, (jboolean)JNI_FALSE); (*env)->DeleteLocalRef(env, jp); if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; } int64_t h = el_widget_alloc(EL_WIDGET_TEXTAREA, (int)slot); el_jni_detach_if_attached(); return h; } int64_t el_android_image_create(const char *path) { JNIEnv *env = el_jni_env(); if (!env || !g_bridge_class) return -1; jstring jp = el_jstr(env, path); jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class, g_mid_createImageView, jp); (*env)->DeleteLocalRef(env, jp); if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; } int64_t h = el_widget_alloc(EL_WIDGET_IMAGE, (int)slot); el_jni_detach_if_attached(); return h; } /* ── Widget property setters ─────────────────────────────────────────────── */ void el_android_widget_set_text(int64_t handle, const char *text) { ElWidget *w = el_widget_get(handle); if (!w) return; JNIEnv *env = el_jni_env(); if (!env || !g_bridge_class) return; jstring jt = el_jstr(env, text); (*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setText, (jint)w->slot, jt); (*env)->DeleteLocalRef(env, jt); if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env); el_jni_detach_if_attached(); } const char *el_android_widget_get_text(int64_t handle) { ElWidget *w = el_widget_get(handle); if (!w) return ""; JNIEnv *env = el_jni_env(); if (!env || !g_bridge_class) return ""; jstring js = (jstring)(*env)->CallStaticObjectMethod(env, g_bridge_class, g_mid_getText, (jint)w->slot); if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return ""; } const char *result = ""; if (js) { const char *cstr = (*env)->GetStringUTFChars(env, js, NULL); result = cstr ? strdup(cstr) : ""; if (cstr) (*env)->ReleaseStringUTFChars(env, js, cstr); (*env)->DeleteLocalRef(env, js); } el_jni_detach_if_attached(); return result; } void el_android_widget_set_color(int64_t handle, float r, float g, float b, float a) { ElWidget *w = el_widget_get(handle); if (!w) return; JNIEnv *env = el_jni_env(); if (!env || !g_bridge_class) return; (*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setTextColor, (jint)w->slot, (jfloat)r, (jfloat)g, (jfloat)b, (jfloat)a); if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env); el_jni_detach_if_attached(); } void el_android_widget_set_bg_color(int64_t handle, float r, float g, float b, float a) { ElWidget *w = el_widget_get(handle); if (!w) return; JNIEnv *env = el_jni_env(); if (!env || !g_bridge_class) return; (*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setBackgroundColor, (jint)w->slot, (jfloat)r, (jfloat)g, (jfloat)b, (jfloat)a); if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env); el_jni_detach_if_attached(); } void el_android_widget_set_font(int64_t handle, const char *family, int size, int bold) { ElWidget *w = el_widget_get(handle); if (!w) return; JNIEnv *env = el_jni_env(); if (!env || !g_bridge_class) return; jstring jfam = el_jstr(env, family); (*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setFont, (jint)w->slot, jfam, (jint)size, (jboolean)(bold ? JNI_TRUE : JNI_FALSE)); (*env)->DeleteLocalRef(env, jfam); if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env); el_jni_detach_if_attached(); } void el_android_widget_set_padding(int64_t handle, int top, int right, int bottom, int left) { ElWidget *w = el_widget_get(handle); if (!w) return; JNIEnv *env = el_jni_env(); if (!env || !g_bridge_class) return; (*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setPadding, (jint)w->slot, (jint)top, (jint)right, (jint)bottom, (jint)left); if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env); el_jni_detach_if_attached(); } void el_android_widget_set_width(int64_t handle, int width) { ElWidget *w = el_widget_get(handle); if (!w) return; JNIEnv *env = el_jni_env(); if (!env || !g_bridge_class) return; (*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setWidth, (jint)w->slot, (jint)width); if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env); el_jni_detach_if_attached(); } void el_android_widget_set_height(int64_t handle, int height) { ElWidget *w = el_widget_get(handle); if (!w) return; JNIEnv *env = el_jni_env(); if (!env || !g_bridge_class) return; (*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setHeight, (jint)w->slot, (jint)height); if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env); el_jni_detach_if_attached(); } void el_android_widget_set_flex(int64_t handle, int flex) { ElWidget *w = el_widget_get(handle); if (!w) return; JNIEnv *env = el_jni_env(); if (!env || !g_bridge_class) return; (*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setFlex, (jint)w->slot, (jint)flex); if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env); el_jni_detach_if_attached(); } void el_android_widget_set_corner_radius(int64_t handle, int radius) { ElWidget *w = el_widget_get(handle); if (!w) return; JNIEnv *env = el_jni_env(); if (!env || !g_bridge_class) return; (*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setCornerRadius, (jint)w->slot, (jfloat)radius); if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env); el_jni_detach_if_attached(); } void el_android_widget_set_disabled(int64_t handle, int disabled) { ElWidget *w = el_widget_get(handle); if (!w) return; JNIEnv *env = el_jni_env(); if (!env || !g_bridge_class) return; (*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setEnabled, (jint)w->slot, (jboolean)(disabled ? JNI_FALSE : JNI_TRUE)); if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env); el_jni_detach_if_attached(); } void el_android_widget_set_hidden(int64_t handle, int hidden) { ElWidget *w = el_widget_get(handle); if (!w) return; JNIEnv *env = el_jni_env(); if (!env || !g_bridge_class) return; /* visible=true means NOT hidden */ (*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setVisibility, (jint)w->slot, (jboolean)(hidden ? JNI_FALSE : JNI_TRUE)); if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env); el_jni_detach_if_attached(); } /* ── Child management ─────────────────────────────────────────────────────── */ void el_android_widget_add_child(int64_t parent, int64_t child) { ElWidget *pw = el_widget_get(parent); ElWidget *cw = el_widget_get(child); if (!pw || !cw) return; JNIEnv *env = el_jni_env(); if (!env || !g_bridge_class) return; (*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_addChild, (jint)pw->slot, (jint)cw->slot); if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env); el_jni_detach_if_attached(); } void el_android_widget_remove_child(int64_t parent, int64_t child) { ElWidget *pw = el_widget_get(parent); ElWidget *cw = el_widget_get(child); if (!pw || !cw) return; JNIEnv *env = el_jni_env(); if (!env || !g_bridge_class) return; (*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_removeChild, (jint)pw->slot, (jint)cw->slot); if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env); el_jni_detach_if_attached(); } /* ── Event registration ───────────────────────────────────────────────────── */ void el_android_widget_on_click(int64_t handle, const char *fn_name) { ElWidget *w = el_widget_get(handle); if (!w) return; free(w->cb_click); w->cb_click = (fn_name && *fn_name) ? strdup(fn_name) : NULL; if (!w->cb_click) return; JNIEnv *env = el_jni_env(); if (!env || !g_bridge_class) return; (*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setOnClickListener, (jint)w->slot); if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env); el_jni_detach_if_attached(); } void el_android_widget_on_change(int64_t handle, const char *fn_name) { ElWidget *w = el_widget_get(handle); if (!w) return; free(w->cb_change); w->cb_change = (fn_name && *fn_name) ? strdup(fn_name) : NULL; if (!w->cb_change) return; JNIEnv *env = el_jni_env(); if (!env || !g_bridge_class) return; (*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setOnChangeListener, (jint)w->slot); if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env); el_jni_detach_if_attached(); } void el_android_widget_on_submit(int64_t handle, const char *fn_name) { /* Submit stored in cb_click, same as AppKit. */ ElWidget *w = el_widget_get(handle); if (!w) return; free(w->cb_click); w->cb_click = (fn_name && *fn_name) ? strdup(fn_name) : NULL; if (!w->cb_click) return; JNIEnv *env = el_jni_env(); if (!env || !g_bridge_class) return; (*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setOnSubmitListener, (jint)w->slot); if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env); el_jni_detach_if_attached(); } /* ── Widget destroy ───────────────────────────────────────────────────────── */ void el_android_widget_destroy(int64_t handle) { ElWidget *w = el_widget_get(handle); if (!w) return; JNIEnv *env = el_jni_env(); if (env && g_bridge_class) { (*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_destroyView, (jint)w->slot); if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env); } el_widget_free(handle); el_jni_detach_if_attached(); } /* ── Manifest reader ─────────────────────────────────────────────────────── */ /* * __manifest_read: parse the app{} block from a manifest file. * Returns the raw file contents as an el_val_t (const char* cast). * The caller (el program) parses the returned string. * Reads from the filesystem; for APK assets use the AssetManager path instead. */ static char *el_read_file(const char *path) { if (!path || !*path) return NULL; FILE *f = fopen(path, "rb"); if (!f) return NULL; fseek(f, 0, SEEK_END); long len = ftell(f); fseek(f, 0, SEEK_SET); if (len <= 0) { fclose(f); return NULL; } char *buf = (char *)malloc((size_t)len + 1); if (!buf) { fclose(f); return NULL; } fread(buf, 1, (size_t)len, f); buf[len] = '\0'; fclose(f); return buf; } el_val_t el_android_manifest_read(const char *path) { char *contents = el_read_file(path); if (!contents) return (el_val_t)(uintptr_t)""; return (el_val_t)(uintptr_t)contents; /* caller owns allocation */ } /* ── __widget_* C API (called from el_seed.c) ────────────────────────────── */ /* * These are the functions declared in el_native_target.h under EL_TARGET_ANDROID. * They forward to the el_android_* internal functions above. * * The el_val_t / int64_t ABI matches the AppKit functions exactly: * - Integer params passed as int64_t, extracted with (int) * - String params passed as int64_t, extracted with (const char*)(uintptr_t) * - Float params (r,g,b,a) passed as int64_t bit-cast from double; extracted * with el_to_float / bit-cast union */ static inline float el_val_to_float(el_val_t v) { union { double d; int64_t i; } u; u.i = v; return (float)u.d; } void __native_init(void) { el_android_init(); } void __native_run_loop(void) { /* No-op on Android — lifecycle is driven by the Activity. */ } el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height, el_val_t min_width, el_val_t min_height) { return (el_val_t)el_android_window_create( (const char *)(uintptr_t)title, (int)width, (int)height, (int)min_width, (int)min_height); } void __window_show(el_val_t handle) { el_android_window_show((int64_t)handle); } void __window_set_title(el_val_t handle, el_val_t title) { el_android_window_set_title((int64_t)handle, (const char *)(uintptr_t)title); } el_val_t __vstack_create(el_val_t spacing) { return (el_val_t)el_android_vstack_create((int)spacing); } el_val_t __hstack_create(el_val_t spacing) { return (el_val_t)el_android_hstack_create((int)spacing); } el_val_t __zstack_create(void) { return (el_val_t)el_android_zstack_create(); } el_val_t __scroll_create(void) { return (el_val_t)el_android_scroll_create(); } el_val_t __label_create(el_val_t text) { return (el_val_t)el_android_label_create((const char *)(uintptr_t)text); } el_val_t __button_create(el_val_t label) { return (el_val_t)el_android_button_create((const char *)(uintptr_t)label); } el_val_t __text_field_create(el_val_t placeholder) { return (el_val_t)el_android_text_field_create((const char *)(uintptr_t)placeholder); } el_val_t __text_area_create(el_val_t placeholder) { return (el_val_t)el_android_text_area_create((const char *)(uintptr_t)placeholder); } el_val_t __image_create(el_val_t path_or_name) { return (el_val_t)el_android_image_create((const char *)(uintptr_t)path_or_name); } void __widget_set_text(el_val_t handle, el_val_t text) { el_android_widget_set_text((int64_t)handle, (const char *)(uintptr_t)text); } el_val_t __widget_get_text(el_val_t handle) { return (el_val_t)(uintptr_t)el_android_widget_get_text((int64_t)handle); } void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a) { el_android_widget_set_color((int64_t)handle, el_val_to_float(r), el_val_to_float(g), el_val_to_float(b), el_val_to_float(a)); } void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a) { el_android_widget_set_bg_color((int64_t)handle, el_val_to_float(r), el_val_to_float(g), el_val_to_float(b), el_val_to_float(a)); } void __widget_set_font(el_val_t handle, el_val_t family, el_val_t size, el_val_t bold) { el_android_widget_set_font((int64_t)handle, (const char *)(uintptr_t)family, (int)size, (int)bold); } void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right, el_val_t bottom, el_val_t left) { el_android_widget_set_padding((int64_t)handle, (int)top, (int)right, (int)bottom, (int)left); } void __widget_set_width(el_val_t handle, el_val_t width) { el_android_widget_set_width((int64_t)handle, (int)width); } void __widget_set_height(el_val_t handle, el_val_t height) { el_android_widget_set_height((int64_t)handle, (int)height); } void __widget_set_flex(el_val_t handle, el_val_t flex) { el_android_widget_set_flex((int64_t)handle, (int)flex); } void __widget_set_corner_radius(el_val_t handle, el_val_t radius) { el_android_widget_set_corner_radius((int64_t)handle, (int)radius); } void __widget_set_disabled(el_val_t handle, el_val_t disabled) { el_android_widget_set_disabled((int64_t)handle, (int)disabled); } void __widget_set_hidden(el_val_t handle, el_val_t hidden) { el_android_widget_set_hidden((int64_t)handle, (int)hidden); } void __widget_add_child(el_val_t parent, el_val_t child) { el_android_widget_add_child((int64_t)parent, (int64_t)child); } void __widget_remove_child(el_val_t parent, el_val_t child) { el_android_widget_remove_child((int64_t)parent, (int64_t)child); } void __widget_destroy(el_val_t handle) { el_android_widget_destroy((int64_t)handle); } void __widget_on_click(el_val_t handle, el_val_t fn_name) { el_android_widget_on_click((int64_t)handle, (const char *)(uintptr_t)fn_name); } void __widget_on_change(el_val_t handle, el_val_t fn_name) { el_android_widget_on_change((int64_t)handle, (const char *)(uintptr_t)fn_name); } void __widget_on_submit(el_val_t handle, el_val_t fn_name) { el_android_widget_on_submit((int64_t)handle, (const char *)(uintptr_t)fn_name); } el_val_t __manifest_read(el_val_t path) { return el_android_manifest_read((const char *)(uintptr_t)path); } #endif /* EL_TARGET_ANDROID */