use std::ffi::c_void;
use std::sync::atomic::Ordering;
use jni::errors::{Error as JniError, Result as JniResult};
use jni::objects::{JClass, JObject, JString};
use jni::sys::{jboolean, jfloat, jint, jlong};
use jni::NativeMethod;
use jni::EnvUnowned;
use ndk::native_window::NativeWindow;
use crate::{bridge_class_name, jni_static};
use crate::renderer::render_loop;
use crate::state::{TouchAction, TouchEvent, app_state, init_app_state};
pub fn build_native_methods() -> Vec<NativeMethod<'static>> {
vec![
unsafe {
NativeMethod::from_raw_parts(
jni_static("nativeAttach"),
jni_static("(Landroid/app/Activity;Landroid/view/Surface;IIF)Z"),
native_attach as *mut c_void,
)
},
unsafe {
NativeMethod::from_raw_parts(
jni_static("nativeOnTouch"),
jni_static("(FFI)Z"),
native_on_touch as *mut c_void,
)
},
unsafe {
NativeMethod::from_raw_parts(
jni_static("nativeOnTextEditResult"),
jni_static("(JLjava/lang/String;)V"),
native_on_text_edit_result as *mut c_void,
)
},
unsafe {
NativeMethod::from_raw_parts(
jni_static("nativeOnTextEditCancelled"),
jni_static("()V"),
native_on_text_edit_cancelled as *mut c_void,
)
},
unsafe {
NativeMethod::from_raw_parts(
jni_static("nativeOnImagePicked"),
jni_static("(JLjava/lang/String;)V"),
native_on_image_picked as *mut c_void,
)
},
unsafe {
NativeMethod::from_raw_parts(
jni_static("nativeStop"),
jni_static("()V"),
native_stop as *mut c_void,
)
},
]
}
pub extern "system" fn native_attach<'caller>(
mut unowned: EnvUnowned<'caller>,
_class: JClass<'caller>,
_activity: JObject<'caller>,
surface: JObject<'caller>,
width: jint,
height: jint,
density: jfloat,
) -> jboolean {
unowned
.with_env(|env| -> JniResult<jboolean> {
let java_vm = env.get_java_vm()?.clone();
let class = env.find_class(jni::strings::JNIString::from(bridge_class_name()))?;
let class = env.new_global_ref(class)?;
let window = unsafe {
let raw_env: *mut jni_sys_03::JNIEnv =
env.get_raw() as *mut jni_sys_03::JNIEnv;
NativeWindow::from_surface(raw_env, surface.as_raw())
.ok_or_else(|| JniError::NullPtr("ANativeWindow_fromSurface"))?
};
let state = init_app_state();
{
*state.java_vm.lock().unwrap() = Some(java_vm);
*state.egui_class.lock().unwrap() = Some(class);
*state.density.lock().unwrap() = density;
*state.size_px.lock().unwrap() = (width.max(1) as u32, height.max(1) as u32);
*state.window.lock().unwrap() = Some(window);
state.touches.lock().unwrap().clear();
state.extra_events.lock().unwrap().clear();
crate::state::TEXT_BRIDGE.lock().unwrap().dialog_open = false;
crate::state::IMAGE_PICKER.lock().unwrap().open = false;
}
{
let mut guard = state.render_thread.lock().unwrap();
if guard.as_ref().is_some_and(|h| h.is_finished()) {
guard.take(); }
state.running.store(true, Ordering::SeqCst);
match guard.as_ref() {
Some(_) => state.request_rebind(),
None => {
let thread_state = state.clone();
*guard = Some(std::thread::spawn(move || render_loop(thread_state)));
}
}
}
Ok(jni::sys::JNI_TRUE)
})
.resolve::<jni::errors::LogErrorAndDefault>()
}
pub extern "system" fn native_on_touch<'caller>(
mut unowned: EnvUnowned<'caller>,
_class: JClass<'caller>,
x_px: jfloat,
y_px: jfloat,
action: jint,
) -> jboolean {
unowned
.with_env(|_env| -> JniResult<jboolean> {
let state = app_state();
let density = *state.density.lock().unwrap();
if density <= 0.0 {
return Ok(jni::sys::JNI_FALSE);
}
let touch_action = match action {
0 => TouchAction::Down,
1 => TouchAction::Up,
2 => TouchAction::Move,
_ => return Ok(jni::sys::JNI_FALSE),
};
let pos = egui::pos2(x_px / density, y_px / density);
if crate::state::TEXT_BRIDGE.lock().unwrap().dialog_open {
return Ok(jni::sys::JNI_TRUE);
}
let consume = if touch_action == TouchAction::Down {
match state.egui_ctx.lock().unwrap().clone() {
Some(ctx) => {
let wants_pointer = ctx.egui_wants_pointer_input();
let wants_keyboard = ctx.egui_wants_keyboard_input();
let over_egui = ctx
.layer_id_at(pos)
.is_some_and(|layer| layer.order != egui::Order::Background);
let (_px, _py) = (pos.x, pos.y);
wants_pointer || wants_keyboard || over_egui
}
None => false,
}
} else {
true
};
if !consume {
return Ok(jni::sys::JNI_FALSE);
}
state.push_touch(TouchEvent {
x: pos.x,
y: pos.y,
action: touch_action,
});
Ok(jni::sys::JNI_TRUE)
})
.resolve::<jni::errors::LogErrorAndDefault>()
}
pub extern "system" fn native_on_text_edit_result<'caller>(
mut unowned: EnvUnowned<'caller>,
_class: JClass<'caller>,
request_id: jlong,
text: JString<'caller>,
) {
unowned
.with_env(|env| -> JniResult<()> {
let new_text = text.mutf8_chars(env)?.to_str().into_owned();
app_state().push_text_result(request_id as u64, new_text);
Ok(())
})
.resolve::<jni::errors::LogErrorAndDefault>()
}
pub extern "system" fn native_on_text_edit_cancelled<'caller>(
mut unowned: EnvUnowned<'caller>,
_class: JClass<'caller>,
) {
unowned
.with_env(|_env| -> JniResult<()> {
crate::state::TEXT_BRIDGE.lock().unwrap().dialog_open = false;
Ok(())
})
.resolve::<jni::errors::LogErrorAndDefault>()
}
pub extern "system" fn native_on_image_picked<'caller>(
mut unowned: EnvUnowned<'caller>,
_class: JClass<'caller>,
request_id: jlong,
saved_path: JString<'caller>,
) {
unowned
.with_env(|env| -> JniResult<()> {
let path = if saved_path.is_null() {
None
} else {
Some(saved_path.mutf8_chars(env)?.to_str().into_owned())
};
app_state().push_image_result(request_id as u64, path);
Ok(())
})
.resolve::<jni::errors::LogErrorAndDefault>()
}
pub extern "system" fn native_stop<'caller>(
mut unowned: EnvUnowned<'caller>,
_class: JClass<'caller>,
) {
unowned
.with_env(|_env| -> JniResult<()> {
let state = app_state();
state.request_stop();
*state.window.lock().unwrap() = None;
Ok(())
})
.resolve::<jni::errors::LogErrorAndDefault>()
}