waterui-ffi 0.3.0

FFI bindings for the WaterUI cross-platform UI framework
//! JNI bridge functions for WaterUI Android.
//!
//! This module provides the JNI function implementations that Kotlin expects
//! for functions NOT generated by macros (ffi_view!, ffi_binding!, opaque!, etc.).
//!
//! Most component-specific functions are auto-generated by macros when
//! the `android-jni` feature is enabled.

extern crate alloc;
extern crate std;

use core::ffi::c_void;
use jni::objects::{Global, JClass, JObject, JValue};
use jni::sys::{jint, jlong, jobject};
use jni::{Env, EnvUnowned, jni_sig, jni_str};

use super::type_id_to_java;
use crate::{IntoFFI, IntoRust, WuiAnyView, WuiTypeId, app::WuiAndroidAppHandles};

// These functions are generated by the export!() macro in the user's app
// and linked at runtime via the same shared library.
unsafe extern "C" {
    fn waterui_android_init() -> *mut c_void;
    fn waterui_android_app(env: *mut c_void) -> WuiAndroidAppHandles;
}

// ============================================================================
// Core Functions (not generated by macros)
// ============================================================================

/// Initialize the WaterUI runtime and return an environment pointer.
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_init<'local>(
    _env: EnvUnowned<'local>,
    _class: JClass<'local>,
) -> jlong {
    unsafe { waterui_android_init() as jlong }
}

/// Create the application from the environment.
/// Returns an AppStruct jobject.
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_app<'local>(
    mut env: EnvUnowned<'local>,
    _class: JClass<'local>,
    env_ptr: jlong,
) -> jobject {
    let app = unsafe { waterui_android_app(env_ptr as *mut c_void) };
    super::with_env(&mut env, |env| app_to_java(env, app).into_raw())
}

/// Convert the two transferred Android app handles to Java AppStruct.
fn app_to_java<'local>(env: &mut Env<'local>, app: WuiAndroidAppHandles) -> JObject<'local> {
    let app_struct_class = env
        .find_class(jni_str!("dev/waterui/android/runtime/AppStruct"))
        .expect("AppStruct class not found");
    env.new_object(
        &app_struct_class,
        jni_sig!("(JJ)V"),
        &[
            JValue::Long(app.content as jlong),
            JValue::Long(app.env as jlong),
        ],
    )
    .expect("Failed to create AppStruct")
}

/// Get the type ID of a view.
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_viewId<'local>(
    mut env: EnvUnowned<'local>,
    _class: JClass<'local>,
    view_ptr: jlong,
) -> jobject {
    let view = unsafe { &*(view_ptr as *const crate::WuiAnyView) };
    let type_id = unsafe { crate::waterui_view_id(view) };
    super::with_env(&mut env, |env| type_id_to_java(env, type_id).into_raw())
}

/// Get the body of a composite view.
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_viewBody<'local>(
    _env: EnvUnowned<'local>,
    _class: JClass<'local>,
    view_ptr: jlong,
    env_ptr: jlong,
) -> jlong {
    let view = view_ptr as *mut crate::WuiAnyView;
    let wui_env = env_ptr as *mut crate::WuiEnv;
    unsafe { crate::waterui_view_body(view, wui_env) as jlong }
}

/// Get the stretch axis of a view.
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_viewStretchAxis<'local>(
    _env: EnvUnowned<'local>,
    _class: JClass<'local>,
    view_ptr: jlong,
) -> jint {
    let view = view_ptr as *const crate::WuiAnyView;
    unsafe { crate::waterui_view_stretch_axis(view) as jint }
}

/// Clone an environment.
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_cloneEnv<'local>(
    _env: EnvUnowned<'local>,
    _class: JClass<'local>,
    env_ptr: jlong,
) -> jlong {
    let wui_env = env_ptr as *const crate::WuiEnv;
    unsafe { crate::waterui_clone_env(wui_env) as jlong }
}

// dropEnv is generated by opaque!; AnyView ownership transfers directly.

// ============================================================================
// Type ID Functions (not generated by ffi_view! macro)
// ============================================================================

/// Get the empty type ID.
/// This is special - () doesn't use ffi_view! macro.
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_emptyId<'local>(
    mut env: EnvUnowned<'local>,
    _class: JClass<'local>,
) -> jobject {
    let type_id = WuiTypeId::of::<()>();
    super::with_env(&mut env, |env| type_id_to_java(env, type_id).into_raw())
}

/// Get the spacer type ID.
/// Spacer is defined specially in layout.rs.
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_spacerId<'local>(
    mut env: EnvUnowned<'local>,
    _class: JClass<'local>,
) -> jobject {
    let type_id = WuiTypeId::of::<waterui_core::Native<waterui::component::spacer::Spacer>>();
    super::with_env(&mut env, |env| type_id_to_java(env, type_id).into_raw())
}

#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_appliedFilterId<'local>(
    mut env: EnvUnowned<'local>,
    _class: JClass<'local>,
) -> jobject {
    let type_id = WuiTypeId::of::<waterui_core::Metadata<waterui_graphics::AppliedFilter>>();
    super::with_env(&mut env, |env| type_id_to_java(env, type_id).into_raw())
}

#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_viewEffectId<'local>(
    mut env: EnvUnowned<'local>,
    _class: JClass<'local>,
) -> jobject {
    let type_id =
        WuiTypeId::of::<waterui_core::Native<waterui_graphics::view_effect::ViewEffectErased>>();
    super::with_env(&mut env, |env| type_id_to_java(env, type_id).into_raw())
}

#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_webViewId<'local>(
    mut env: EnvUnowned<'local>,
    _class: JClass<'local>,
) -> jobject {
    let type_id = WuiTypeId::of::<waterui_core::Native<waterui_webview::WebView>>();
    super::with_env(&mut env, |env| type_id_to_java(env, type_id).into_raw())
}

#[unsafe(no_mangle)]
/// Forces an owned view handle into its WebView descriptor.
///
/// # Safety
///
/// `view_ptr` must be a valid owning view pointer whose type id is WebView, and
/// must not be used after this call.
unsafe extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_forceAsWebView<'local>(
    mut env: EnvUnowned<'local>,
    _class: JClass<'local>,
    view_ptr: jlong,
) -> jobject {
    super::with_env(&mut env, |env| unsafe {
        let view_ptr: *mut WuiAnyView = crate::jni::convert::jlong_to_ptr_mut(view_ptr);
        let any: waterui::AnyView = view_ptr.into_rust();
        let view = *any.downcast_unchecked::<waterui_core::Native<waterui_webview::WebView>>();
        let ffi = view.into_inner().into_ffi();
        crate::jni::convert::struct_to_java(env, &ffi).into_raw()
    })
}

#[unsafe(no_mangle)]
/// Drops an owned reactive view-collection handle.
///
/// # Safety
///
/// `ptr` must be a valid owning `WuiAnyViews` pointer and must not have been
/// dropped previously.
unsafe extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_dropAnyViews<'local>(
    _env: EnvUnowned<'local>,
    _class: JClass<'local>,
    ptr: jlong,
) {
    unsafe {
        drop(crate::IntoRust::into_rust(
            ptr as *mut crate::views::WuiAnyViews,
        ));
    }
}

/// Drops an owned type-erased view that was never transferred into a renderer.
///
/// # Safety
///
/// `ptr` must be a valid owning `WuiAnyView` pointer and must not have been
/// consumed or dropped previously.
#[unsafe(no_mangle)]
unsafe extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_dropAnyView<'local>(
    _env: EnvUnowned<'local>,
    _class: JClass<'local>,
    ptr: jlong,
) {
    unsafe {
        drop(crate::IntoRust::into_rust(ptr as *mut crate::WuiAnyView));
    }
}

// ============================================================================
// Environment Install Functions
// ============================================================================

#[cfg(feature = "gpu")]
struct AndroidGpuRuntimeCompletion {
    jvm: jni::JavaVM,
    callback: Global<JObject<'static>>,
}

#[cfg(feature = "gpu")]
impl AndroidGpuRuntimeCompletion {
    fn complete(self, runtime: waterui_graphics::shared_context::GpuRuntime) {
        let runtime = crate::IntoFFI::into_ffi(runtime);
        super::with_attached_env(&self.jvm, |env| {
            env.call_method(
                &self.callback,
                jni_str!("onReady"),
                jni_sig!("(J)V"),
                &[JValue::Long(runtime as jlong)],
            )
            .expect("GpuRuntimeReadyCallback.onReady failed");
        })
        .expect("GPU runtime completion failed to attach to JVM");
    }
}

/// Creates the process GPU runtime without blocking the Android main thread.
#[cfg(feature = "gpu")]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_gpuRuntimeCreate<'local>(
    mut env: EnvUnowned<'local>,
    _class: JClass<'local>,
    callback: JObject<'local>,
) {
    super::with_env(&mut env, |env| {
        let completion = AndroidGpuRuntimeCompletion {
            jvm: env
                .get_java_vm()
                .expect("WatcherJni.gpuRuntimeCreate failed to access JavaVM"),
            callback: env
                .new_global_ref(callback)
                .expect("WatcherJni.gpuRuntimeCreate failed to retain callback"),
        };
        crate::components::gpu_runtime::create_gpu_runtime(move |runtime| {
            completion.complete(runtime);
        });
    });
}

/// Installs an asynchronously-created GPU runtime into the app environment.
#[cfg(feature = "gpu")]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_envInstallGpuRuntime<'local>(
    _env: EnvUnowned<'local>,
    _class: JClass<'local>,
    env_ptr: jlong,
    runtime_ptr: jlong,
) {
    let env = unsafe { crate::borrow_ffi_mut(env_ptr as *mut crate::WuiEnv) };
    let runtime = unsafe {
        crate::IntoRust::into_rust(
            runtime_ptr as *mut crate::components::gpu_runtime::WuiGpuRuntime,
        )
    };
    crate::components::gpu_runtime::install_gpu_runtime(&mut env.0, runtime);
}

/// Install web view controller in the environment.
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_envInstallWebViewController<'local>(
    mut env: EnvUnowned<'local>,
    _class: JClass<'local>,
    env_ptr: jlong,
    factory: JObject<'local>,
) {
    super::with_env(&mut env, |env| unsafe {
        super::webview_bridge::install_android_webview_controller(
            env,
            env_ptr as *mut crate::WuiEnv,
            factory,
        );
    });
}