extern crate alloc;
extern crate std;
use alloc::boxed::Box;
use alloc::rc::Rc;
use alloc::vec::Vec;
use jni::objects::{Global, JClass, JObject, JObjectArray, JString, JValue};
use jni::signature::MethodSignature;
use jni::strings::JNIStr;
use jni::sys::{jint, jlong, jobject, jobjectArray};
use jni::{Env, EnvUnowned, jni_sig, jni_str};
use crate::components::text::WuiHorizontalAlignment;
use crate::cursor::WuiCursorStyle;
use crate::id::WuiId;
use crate::reactive::{WuiBinding, WuiComputed, WuiWatcherGuard};
use crate::theme::WuiColorScheme;
use crate::{IntoFFI, IntoRust};
const WATCHER_STRUCT_CLASS: &JNIStr = jni_str!("dev/waterui/android/runtime/WatcherStruct");
const WATCHER_STRUCT_CTOR: &MethodSignature<'static, 'static> = &jni_sig!("(JJJ)V");
const WATCHER_METADATA_CLASS: &JNIStr = jni_str!("dev/waterui/android/reactive/WuiWatcherMetadata");
const WATCHER_METADATA_CTOR: &MethodSignature<'static, 'static> = &jni_sig!("(J)V");
const RESOLVED_COLOR_CLASS: &JNIStr = jni_str!("dev/waterui/android/runtime/ResolvedColorStruct");
const RESOLVED_COLOR_CTOR: &MethodSignature<'static, 'static> = &jni_sig!("(FFFFF)V");
const RESOLVED_FONT_CLASS: &JNIStr = jni_str!("dev/waterui/android/runtime/ResolvedFontStruct");
const RESOLVED_FONT_CTOR: &MethodSignature<'static, 'static> = &jni_sig!("(FILjava/lang/String;)V");
const DATE_CLASS: &JNIStr = jni_str!("dev/waterui/android/runtime/DateStruct");
const DATE_CTOR: &MethodSignature<'static, 'static> = &jni_sig!("(III)V");
const DATE_TIME_CLASS: &JNIStr = jni_str!("dev/waterui/android/runtime/DateTimeStruct");
const DATE_TIME_CTOR: &MethodSignature<'static, 'static> = &jni_sig!("(IIIIII)V");
const TEXT_STYLE_CLASS: &JNIStr = jni_str!("dev/waterui/android/runtime/TextStyleStruct");
const TEXT_STYLE_CTOR: &MethodSignature<'static, 'static> = &jni_sig!("(JZZZJJ)V");
const STYLED_CHUNK_CLASS: &JNIStr = jni_str!("dev/waterui/android/runtime/StyledChunkStruct");
const STYLED_CHUNK_CTOR: &MethodSignature<'static, 'static> =
&jni_sig!("(Ljava/lang/String;Ldev/waterui/android/runtime/TextStyleStruct;)V");
const STYLED_STR_CLASS: &JNIStr = jni_str!("dev/waterui/android/runtime/StyledStrStruct");
const STYLED_STR_CTOR: &MethodSignature<'static, 'static> =
&jni_sig!("([Ldev/waterui/android/runtime/StyledChunkStruct;)V");
trait JniIntValue {
fn into_jint(self) -> jint;
}
impl JniIntValue for WuiColorScheme {
fn into_jint(self) -> jint {
self as jint
}
}
impl JniIntValue for WuiCursorStyle {
fn into_jint(self) -> jint {
self as jint
}
}
impl JniIntValue for WuiHorizontalAlignment {
fn into_jint(self) -> jint {
self as jint
}
}
impl JniIntValue for WuiId {
fn into_jint(self) -> jint {
self.inner
}
}
fn read_computed_jni_int<T>(computed_ptr: jlong) -> jint
where
T: IntoFFI + 'static,
T::FFI: JniIntValue,
{
use waterui::Signal;
let computed = unsafe { &*(computed_ptr as *const WuiComputed<T>) };
computed.get().into_ffi().into_jint()
}
fn create_resolved_color_struct<'local>(
env: &mut Env<'local>,
red: f32,
green: f32,
blue: f32,
opacity: f32,
headroom: f32,
) -> JObject<'local> {
env.new_object(
RESOLVED_COLOR_CLASS,
RESOLVED_COLOR_CTOR,
&[
JValue::Float(red),
JValue::Float(green),
JValue::Float(blue),
JValue::Float(opacity),
JValue::Float(headroom),
],
)
.expect("Failed to create ResolvedColorStruct")
}
fn create_resolved_font_struct<'local>(
env: &mut Env<'local>,
size: f32,
weight: i32,
family: Option<&str>,
) -> JObject<'local> {
let family: JObject<'local> = match family {
Some(family) => env
.new_string(family)
.expect("Failed to create font family string")
.into(),
None => JObject::null(),
};
env.new_object(
RESOLVED_FONT_CLASS,
RESOLVED_FONT_CTOR,
&[
JValue::Float(size),
JValue::Int(weight),
JValue::Object(&family),
],
)
.expect("Failed to create ResolvedFontStruct")
}
fn create_date_struct<'local>(
env: &mut Env<'local>,
year: i32,
month: i32,
day: i32,
) -> JObject<'local> {
env.new_object(
DATE_CLASS,
DATE_CTOR,
&[JValue::Int(year), JValue::Int(month), JValue::Int(day)],
)
.expect("Failed to create DateStruct")
}
fn create_date_time_struct<'local>(
env: &mut Env<'local>,
year: i32,
month: i32,
day: i32,
hour: i32,
minute: i32,
second: i32,
) -> JObject<'local> {
env.new_object(
DATE_TIME_CLASS,
DATE_TIME_CTOR,
&[
JValue::Int(year),
JValue::Int(month),
JValue::Int(day),
JValue::Int(hour),
JValue::Int(minute),
JValue::Int(second),
],
)
.expect("Failed to create DateTimeStruct")
}
fn read_binding_str_to_java_string(env: &mut Env, binding_ptr: jlong) -> jobject {
let binding = unsafe { &*(binding_ptr as *const WuiBinding<waterui::Str>) };
let s: waterui::Str = binding.get();
env.new_string(s.as_str())
.expect("read_binding_str_to_java_string: failed to create Java string")
.into_raw()
}
fn set_binding_str_from_java_string<'local>(
env: &mut Env<'local>,
binding_ptr: jlong,
value: &JString<'local>,
) {
let binding = unsafe { &*(binding_ptr as *const WuiBinding<waterui::Str>) };
let value = crate::jni::convert::string_from_java(env, value);
binding.set(waterui::Str::from(value));
}
macro_rules! jni_binding_str {
($name:tt) => {
pastey::paste! {
#[unsafe(no_mangle)]
extern "system" fn [<Java_dev_waterui_android_ffi_WatcherJni_readBinding $name:camel>]<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
binding_ptr: jlong,
) -> jobject {
super::with_env(&mut env, |env| {
read_binding_str_to_java_string(env, binding_ptr)
})
}
#[unsafe(no_mangle)]
extern "system" fn [<Java_dev_waterui_android_ffi_WatcherJni_setBinding $name:camel>]<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
binding_ptr: jlong,
value: JString<'local>,
) {
super::with_env(&mut env, |env| {
set_binding_str_from_java_string(env, binding_ptr, &value);
});
}
}
};
}
jni_binding_str!(str);
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_readBindingSecure<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
binding_ptr: jlong,
) -> jobject {
use waterui_form::secure::Secure;
let binding = unsafe { &*(binding_ptr as *const WuiBinding<Secure>) };
super::with_env(&mut env, |env| {
env.new_string(binding.get().expose())
.expect("readBindingSecure failed to create Java string")
.into_raw()
})
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_setBindingSecure<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
binding_ptr: jlong,
value: JString<'local>,
) {
use waterui_form::secure::Secure;
let binding = unsafe { &*(binding_ptr as *const WuiBinding<Secure>) };
super::with_env(&mut env, |env| {
let value = crate::jni::convert::string_from_java(env, &value);
binding.set(Secure::new(value));
});
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_watchBindingSecure<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
binding_ptr: jlong,
watcher: JObject<'local>,
) -> jlong {
use nami::Signal;
use waterui_form::secure::Secure;
let (data_ptr, call_ptr, drop_ptr) = super::with_env(&mut env, |env| {
crate::jni::extract_watcher_struct(env, &watcher)
});
let call_fn: unsafe extern "C" fn(
*mut (),
crate::WuiStr,
*mut crate::reactive::WuiWatcherMetadata,
) = unsafe { core::mem::transmute(call_ptr as *const ()) };
let drop_fn: unsafe extern "C" fn(*mut ()) =
unsafe { core::mem::transmute(drop_ptr as *const ()) };
let watcher = unsafe {
crate::reactive::WuiWatcher::<Secure>::new(data_ptr as *mut (), call_fn, drop_fn)
}
.into_inner();
let binding = unsafe { &*(binding_ptr as *const WuiBinding<Secure>) };
let guard = binding.watch(move |context| {
let callback = Rc::clone(&watcher);
callback(context);
});
Box::into_raw(Box::new(WuiWatcherGuard(Box::new(guard)))) as jlong
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_readBindingId<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
binding_ptr: jlong,
) -> jint {
use waterui_core::id::Id;
let binding = unsafe { &*(binding_ptr as *const WuiBinding<Id>) };
binding.get().into_ffi().into_jint()
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_setBindingId<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
binding_ptr: jlong,
value: jint,
) {
use waterui_core::id::Id;
let binding = unsafe { &*(binding_ptr as *const WuiBinding<Id>) };
let value = unsafe { WuiId { inner: value }.into_rust() };
binding.set(value);
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_readBindingStyledStrPlain<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
binding_ptr: jlong,
) -> jobject {
use waterui_text::styled::StyledStr;
let binding = unsafe { &*(binding_ptr as *const WuiBinding<StyledStr>) };
super::with_env(&mut env, |env| {
env.new_string(binding.get().to_plain().as_str())
.expect("readBindingStyledStrPlain failed to create Java string")
.into_raw()
})
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_setBindingStyledStrPlain<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
binding_ptr: jlong,
value: JString<'local>,
) {
use waterui_text::styled::StyledStr;
let binding = unsafe { &*(binding_ptr as *const WuiBinding<StyledStr>) };
super::with_env(&mut env, |env| {
let value = crate::jni::convert::string_from_java(env, &value);
binding.set(StyledStr::plain(value));
});
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_readBindingDateVec<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
binding_ptr: jlong,
) -> jobjectArray {
use waterui_form::picker::date::Date;
let binding = unsafe { &*(binding_ptr as *mut WuiBinding<Vec<Date>>) };
let dates = binding.get();
super::with_env(&mut env, |env| {
let date_class = env
.find_class(jni_str!("dev/waterui/android/runtime/DateStruct"))
.expect("DateStruct class not found");
let array = env
.new_object_array(super::array_len(dates.len()), &date_class, JObject::null())
.expect("Failed to create date struct array");
for (index, date) in dates.into_iter().enumerate() {
let ffi = date.into_ffi();
let java_date = create_date_struct(env, ffi.year, ffi.month as i32, ffi.day as i32);
array
.set_element(env, index, java_date)
.expect("Failed to write date struct array element");
}
array.into_raw()
})
}
#[unsafe(no_mangle)]
unsafe extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_setBindingDateVec<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
binding_ptr: jlong,
dates: jobjectArray,
) {
use crate::IntoRust;
use crate::components::form::WuiDate;
use waterui_form::picker::date::Date;
let binding = unsafe { &*(binding_ptr as *mut WuiBinding<Vec<Date>>) };
super::with_env(&mut env, |env| {
let dates = unsafe { JObjectArray::<JObject>::from_raw(env, dates) };
let length = dates.len(env).expect("Failed to get date array length");
let mut rust_dates = Vec::with_capacity(length);
for index in 0..length {
let element = dates
.get_element(env, index)
.expect("Failed to read date array element");
let year = env
.get_field(&element, jni_str!("year"), jni_sig!("I"))
.expect("DateStruct.year field missing")
.i()
.expect("DateStruct.year must be int");
let month = env
.get_field(&element, jni_str!("month"), jni_sig!("I"))
.expect("DateStruct.month field missing")
.i()
.expect("DateStruct.month must be int");
let day = env
.get_field(&element, jni_str!("day"), jni_sig!("I"))
.expect("DateStruct.day field missing")
.i()
.expect("DateStruct.day must be int");
let ffi_date = WuiDate {
year,
month: month as u8,
day: day as u8,
};
rust_dates.push(unsafe { ffi_date.into_rust() });
}
binding.set(rust_dates);
});
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_readBindingDateTime<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
binding_ptr: jlong,
) -> jobject {
use crate::IntoFFI;
use jiff::civil::DateTime;
let binding = unsafe { &*(binding_ptr as *mut WuiBinding<DateTime>) };
let date_time: DateTime = binding.get();
let ffi = date_time.into_ffi();
super::with_env(&mut env, |env| {
create_date_time_struct(
env,
ffi.year,
ffi.month as i32,
ffi.day as i32,
ffi.hour as i32,
ffi.minute as i32,
ffi.second as i32,
)
.into_raw()
})
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_setBindingDateTime<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
binding_ptr: jlong,
year: jint,
month: jint,
day: jint,
hour: jint,
minute: jint,
second: jint,
) {
use crate::IntoRust;
use crate::components::form::WuiDateTime;
use jiff::civil::DateTime;
let binding = unsafe { &*(binding_ptr as *mut WuiBinding<DateTime>) };
let wui_date_time = WuiDateTime {
year,
month: month as u8,
day: day as u8,
hour: hour as u8,
minute: minute as u8,
second: second as u8,
};
let date_time = unsafe { wui_date_time.into_rust() };
binding.set(date_time);
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_dropBindingSecure<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
binding_ptr: jlong,
) {
use waterui_form::secure::Secure;
unsafe { drop(Box::from_raw(binding_ptr as *mut WuiBinding<Secure>)) };
}
macro_rules! jni_read_computed_int {
($name:ident, $ty:ty) => {
pastey::paste! {
#[unsafe(no_mangle)]
extern "system" fn [<Java_dev_waterui_android_ffi_WatcherJni_readComputed $name>]<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
computed_ptr: jlong,
) -> jint {
read_computed_jni_int::<$ty>(computed_ptr)
}
}
};
}
jni_read_computed_int!(ColorScheme, waterui::theme::ColorScheme);
jni_read_computed_int!(CursorStyle, waterui::cursor::CursorStyle);
jni_read_computed_int!(HorizontalAlignment, waterui::layout::HorizontalAlignment);
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_readComputedResolvedColor<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
computed_ptr: jlong,
) -> jobject {
use crate::IntoFFI;
use crate::color::WuiResolvedColor;
use waterui::Signal;
use waterui_graphics::color::ResolvedColor;
let computed = unsafe { &*(computed_ptr as *mut WuiComputed<ResolvedColor>) };
let resolved: ResolvedColor = computed.get();
let ffi: WuiResolvedColor = resolved.into_ffi();
super::with_env(&mut env, |env| {
create_resolved_color_struct(env, ffi.red, ffi.green, ffi.blue, ffi.opacity, ffi.headroom)
.into_raw()
})
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_readComputedResolvedFont<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
computed_ptr: jlong,
) -> jobject {
use crate::IntoFFI;
use waterui::Signal;
use waterui_text::font::ResolvedFont;
let computed = unsafe { &*(computed_ptr as *mut WuiComputed<ResolvedFont>) };
let resolved: ResolvedFont = computed.get();
let ffi = resolved.into_ffi();
let weight_int = ffi.weight as i32;
let family: waterui::Str = unsafe { crate::IntoRust::into_rust(ffi.family) };
let family = (!family.is_empty()).then(|| family.as_str());
super::with_env(&mut env, |env| {
create_resolved_font_struct(env, ffi.size, weight_int, family).into_raw()
})
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_readComputedStyledStr<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
computed_ptr: jlong,
) -> jobject {
use waterui::Signal;
use waterui_text::styled::StyledStr;
let computed = unsafe { &*(computed_ptr as *mut WuiComputed<StyledStr>) };
let styled_str: StyledStr = computed.get();
super::with_env(&mut env, |env| {
styled_str_to_java(env, styled_str).into_raw()
})
}
fn styled_str_to_java<'local>(
env: &mut Env<'local>,
styled_str: waterui_text::styled::StyledStr,
) -> JObject<'local> {
use crate::{IntoFFI, IntoRust};
let mut ffi = styled_str.into_ffi();
let chunks = ffi.chunks.as_mut_slice();
let chunk_count = chunks.len();
let chunk_ptr = chunks.as_mut_ptr();
let styled_chunk_class = env
.find_class(STYLED_CHUNK_CLASS)
.expect("StyledChunkStruct class not found");
let chunk_array = env
.new_object_array(
super::array_len(chunk_count),
&styled_chunk_class,
JObject::null(),
)
.expect("Failed to create chunk array");
for index in 0..chunk_count {
let chunk = unsafe { core::ptr::read(chunk_ptr.add(index)) };
let style = chunk.style;
let text_style = env
.new_object(
TEXT_STYLE_CLASS,
TEXT_STYLE_CTOR,
&[
JValue::Long(style.font as jlong),
JValue::Bool(style.italic),
JValue::Bool(style.underline),
JValue::Bool(style.strikethrough),
JValue::Long(style.foreground as jlong),
JValue::Long(style.background as jlong),
],
)
.expect("Failed to create TextStyleStruct");
let text: waterui::Str = unsafe { chunk.text.into_rust() };
let text_jstring = env
.new_string(text.as_str())
.expect("Failed to create text string");
let styled_chunk = env
.new_object(
STYLED_CHUNK_CLASS,
STYLED_CHUNK_CTOR,
&[JValue::Object(&text_jstring), JValue::Object(&text_style)],
)
.expect("Failed to create StyledChunkStruct");
chunk_array
.set_element(env, index, &styled_chunk)
.expect("Failed to set chunk array element");
}
ffi.chunks.consume();
env.new_object(
STYLED_STR_CLASS,
STYLED_STR_CTOR,
&[JValue::Object(&chunk_array)],
)
.expect("Failed to create StyledStrStruct")
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_readComputedDateVec<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
computed_ptr: jlong,
) -> jobjectArray {
use crate::IntoFFI;
use nami::Signal;
use waterui_form::picker::date::Date;
let computed = unsafe { &*(computed_ptr as *mut WuiComputed<Vec<Date>>) };
let dates = computed.get();
super::with_env(&mut env, |env| {
let date_class = env
.find_class(jni_str!("dev/waterui/android/runtime/DateStruct"))
.expect("DateStruct class not found");
let array = env
.new_object_array(super::array_len(dates.len()), &date_class, JObject::null())
.expect("Failed to create computed date array");
for (index, date) in dates.into_iter().enumerate() {
let ffi = date.into_ffi();
let java_date = create_date_struct(env, ffi.year, ffi.month as i32, ffi.day as i32);
array
.set_element(env, index, java_date)
.expect("Failed to write computed date array element");
}
array.into_raw()
})
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_dropWatcherGuard<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
guard_ptr: jlong,
) {
unsafe { drop(Box::from_raw(guard_ptr as *mut WuiWatcherGuard)) };
}
use jni::JavaVM;
use std::sync::Arc;
struct JavaConstructor {
class: Global<JClass<'static>>,
method: jni::sys::jmethodID,
}
impl JavaConstructor {
fn load(
env: &mut Env,
class_name: &'static JNIStr,
descriptor: &'static MethodSignature<'static, 'static>,
) -> Self {
let class = env
.find_class(class_name)
.unwrap_or_else(|_| panic!("watcher class not found: {class_name}"));
let class = env
.new_global_ref(class)
.unwrap_or_else(|_| panic!("failed to retain watcher class: {class_name}"));
let method = env
.get_method_id(&class, jni_str!("<init>"), descriptor)
.unwrap_or_else(|_| {
panic!(
"watcher constructor not found: {class_name}{}",
descriptor.sig()
)
})
.into_raw();
Self { class, method }
}
fn new_object<'local>(
&self,
env: &mut Env<'local>,
args: &[jni::sys::jvalue],
) -> JObject<'local> {
unsafe {
env.new_object_unchecked(
&self.class,
jni::objects::JMethodID::from_raw(self.method),
args,
)
.expect("failed to invoke cached watcher constructor")
}
}
}
struct WatcherData {
callback: jni::objects::Global<JObject<'static>>,
jvm: Arc<JavaVM>,
metadata_constructor: JavaConstructor,
value_constructors: Vec<(&'static JNIStr, JavaConstructor)>,
}
impl WatcherData {
fn constructor(&self, class_name: &'static JNIStr) -> &JavaConstructor {
self.value_constructors
.iter()
.find_map(|(name, constructor)| (*name == class_name).then_some(constructor))
.unwrap_or_else(|| panic!("watcher does not own constructor for {class_name}"))
}
}
unsafe extern "C" fn watcher_drop(data: *mut ()) {
let _: Box<Rc<WatcherData>> = unsafe { Box::from_raw(data as *mut Rc<WatcherData>) };
}
unsafe fn retain_watcher_data(data: *mut ()) -> Rc<WatcherData> {
unsafe { Rc::clone(&*(data as *const Rc<WatcherData>)) }
}
fn with_watcher_env<R>(
data: *mut (),
operation: impl FnOnce(&mut Env<'_>, &WatcherData) -> R,
) -> R {
let watcher_data = unsafe { retain_watcher_data(data) };
super::with_attached_env(&watcher_data.jvm, |env| operation(env, &watcher_data))
.expect("Failed to attach watcher JVM thread")
}
struct JavaWatcherMetadata<'local> {
object: JObject<'local>,
_owned: Box<crate::reactive::WuiWatcherMetadata>,
}
impl<'local> core::ops::Deref for JavaWatcherMetadata<'local> {
type Target = JObject<'local>;
fn deref(&self) -> &Self::Target {
&self.object
}
}
fn create_metadata_object<'local>(
env: &mut Env<'local>,
watcher_data: &WatcherData,
metadata_ptr: *mut crate::reactive::WuiWatcherMetadata,
) -> JavaWatcherMetadata<'local> {
let owned = unsafe { Box::from_raw(metadata_ptr) };
let object = watcher_data
.metadata_constructor
.new_object(env, &[JValue::Long((&raw const *owned) as jlong).as_jni()]);
JavaWatcherMetadata {
object,
_owned: owned,
}
}
fn invoke_callback<'local>(
env: &mut Env<'local>,
callback: &jni::objects::Global<JObject<'static>>,
value: &JObject<'local>,
metadata: &JObject<'local>,
) {
env.call_method(
callback.as_obj(),
jni_str!("onChanged"),
jni_sig!("(Ljava/lang/Object;Ldev/waterui/android/reactive/WuiWatcherMetadata;)V"),
&[JValue::Object(value), JValue::Object(metadata)],
)
.expect("Failed to call onChanged");
}
unsafe extern "C" fn watcher_call_bool(
data: *mut (),
value: bool,
metadata_ptr: *mut crate::reactive::WuiWatcherMetadata,
) {
with_watcher_env(data, |env, watcher_data| {
let bool_class = env
.find_class(jni_str!("java/lang/Boolean"))
.expect("Boolean class not found");
let java_value = env
.call_static_method(
&bool_class,
jni_str!("valueOf"),
jni_sig!("(Z)Ljava/lang/Boolean;"),
&[JValue::Bool(value)],
)
.expect("Failed to box boolean")
.l()
.expect("Expected object");
let metadata = create_metadata_object(env, watcher_data, metadata_ptr);
invoke_callback(env, &watcher_data.callback, &java_value, &metadata);
});
}
unsafe extern "C" fn watcher_call_int(
data: *mut (),
value: i32,
metadata_ptr: *mut crate::reactive::WuiWatcherMetadata,
) {
with_watcher_env(data, |env, watcher_data| {
let int_class = env
.find_class(jni_str!("java/lang/Integer"))
.expect("Integer class not found");
let java_value = env
.call_static_method(
&int_class,
jni_str!("valueOf"),
jni_sig!("(I)Ljava/lang/Integer;"),
&[JValue::Int(value)],
)
.expect("Failed to box integer")
.l()
.expect("Expected object");
let metadata = create_metadata_object(env, watcher_data, metadata_ptr);
invoke_callback(env, &watcher_data.callback, &java_value, &metadata);
});
}
unsafe extern "C" fn watcher_call_double(
data: *mut (),
value: f64,
metadata_ptr: *mut crate::reactive::WuiWatcherMetadata,
) {
with_watcher_env(data, |env, watcher_data| {
let double_class = env
.find_class(jni_str!("java/lang/Double"))
.expect("Double class not found");
let java_value = env
.call_static_method(
&double_class,
jni_str!("valueOf"),
jni_sig!("(D)Ljava/lang/Double;"),
&[JValue::Double(value)],
)
.expect("Failed to box double")
.l()
.expect("Expected object");
let metadata = create_metadata_object(env, watcher_data, metadata_ptr);
invoke_callback(env, &watcher_data.callback, &java_value, &metadata);
});
}
unsafe extern "C" fn watcher_call_float(
data: *mut (),
value: f32,
metadata_ptr: *mut crate::reactive::WuiWatcherMetadata,
) {
with_watcher_env(data, |env, watcher_data| {
let float_class = env
.find_class(jni_str!("java/lang/Float"))
.expect("Float class not found");
let java_value = env
.call_static_method(
&float_class,
jni_str!("valueOf"),
jni_sig!("(F)Ljava/lang/Float;"),
&[JValue::Float(value)],
)
.expect("Failed to box float")
.l()
.expect("Expected object");
let metadata = create_metadata_object(env, watcher_data, metadata_ptr);
invoke_callback(env, &watcher_data.callback, &java_value, &metadata);
});
}
unsafe extern "C" fn watcher_call_string(
data: *mut (),
value: crate::WuiStr,
metadata_ptr: *mut crate::reactive::WuiWatcherMetadata,
) {
with_watcher_env(data, |env, watcher_data| {
let value: waterui::Str = unsafe { crate::IntoRust::into_rust(value) };
let java_string = env
.new_string(value.as_str())
.expect("Failed to create Java string");
let metadata = create_metadata_object(env, watcher_data, metadata_ptr);
invoke_callback(env, &watcher_data.callback, &java_string, &metadata);
});
}
fn invoke_owned_pointer_callback(
data: *mut (),
value: *mut (),
metadata_ptr: *mut crate::reactive::WuiWatcherMetadata,
) {
with_watcher_env(data, |env, watcher_data| {
let long_class = env
.find_class(jni_str!("java/lang/Long"))
.expect("Long class not found");
let java_value = env
.call_static_method(
&long_class,
jni_str!("valueOf"),
jni_sig!("(J)Ljava/lang/Long;"),
&[JValue::Long(value as jlong)],
)
.expect("Failed to box long")
.l()
.expect("Expected object");
let metadata = create_metadata_object(env, watcher_data, metadata_ptr);
invoke_callback(env, &watcher_data.callback, &java_value, &metadata);
});
}
unsafe extern "C" fn watcher_call_any_view(
data: *mut (),
value: *mut crate::WuiAnyView,
metadata_ptr: *mut crate::reactive::WuiWatcherMetadata,
) {
invoke_owned_pointer_callback(data, value.cast(), metadata_ptr);
}
unsafe extern "C" fn watcher_call_color(
data: *mut (),
value: *mut crate::color::WuiColor,
metadata_ptr: *mut crate::reactive::WuiWatcherMetadata,
) {
invoke_owned_pointer_callback(data, value.cast(), metadata_ptr);
}
unsafe extern "C" fn watcher_call_resolved_color(
data: *mut (),
value: crate::color::WuiResolvedColor,
metadata_ptr: *mut crate::reactive::WuiWatcherMetadata,
) {
with_watcher_env(data, |env, watcher_data| {
let java_value = watcher_data.constructor(RESOLVED_COLOR_CLASS).new_object(
env,
&[
JValue::Float(value.red).as_jni(),
JValue::Float(value.green).as_jni(),
JValue::Float(value.blue).as_jni(),
JValue::Float(value.opacity).as_jni(),
JValue::Float(value.headroom).as_jni(),
],
);
let metadata = create_metadata_object(env, watcher_data, metadata_ptr);
invoke_callback(env, &watcher_data.callback, &java_value, &metadata);
});
}
unsafe extern "C" fn watcher_call_resolved_font(
data: *mut (),
value: crate::components::text::WuiResolvedFont,
metadata_ptr: *mut crate::reactive::WuiWatcherMetadata,
) {
with_watcher_env(data, |env, watcher_data| {
let family: waterui::Str = unsafe { crate::IntoRust::into_rust(value.family) };
let family = if family.is_empty() {
JObject::null()
} else {
env.new_string(family.as_str())
.expect("Failed to create font family string")
.into()
};
let java_value = watcher_data.constructor(RESOLVED_FONT_CLASS).new_object(
env,
&[
JValue::Float(value.size).as_jni(),
JValue::Int(value.weight as i32).as_jni(),
JValue::Object(&family).as_jni(),
],
);
let metadata = create_metadata_object(env, watcher_data, metadata_ptr);
invoke_callback(env, &watcher_data.callback, &java_value, &metadata);
});
}
unsafe extern "C" fn watcher_call_styled_str(
data: *mut (),
value: crate::components::text::WuiStyledStr,
metadata_ptr: *mut crate::reactive::WuiWatcherMetadata,
) {
with_watcher_env(data, |env, watcher_data| {
let java_value = wui_styled_str_to_java(env, watcher_data, value);
let metadata = create_metadata_object(env, watcher_data, metadata_ptr);
invoke_callback(env, &watcher_data.callback, &java_value, &metadata);
});
}
unsafe extern "C" fn watcher_call_styled_str_plain(
data: *mut (),
value: crate::components::text::WuiStyledStr,
metadata_ptr: *mut crate::reactive::WuiWatcherMetadata,
) {
with_watcher_env(data, |env, watcher_data| {
let value: waterui_text::styled::StyledStr = unsafe { crate::IntoRust::into_rust(value) };
let java_value = env
.new_string(value.to_plain().as_str())
.expect("Failed to create plain styled-string value");
let metadata = create_metadata_object(env, watcher_data, metadata_ptr);
invoke_callback(env, &watcher_data.callback, &java_value, &metadata);
});
}
fn wui_styled_str_to_java<'local>(
env: &mut Env<'local>,
watcher_data: &WatcherData,
mut ffi: crate::components::text::WuiStyledStr,
) -> JObject<'local> {
use crate::IntoRust;
let text_style_constructor = watcher_data.constructor(TEXT_STYLE_CLASS);
let styled_chunk_constructor = watcher_data.constructor(STYLED_CHUNK_CLASS);
let styled_str_constructor = watcher_data.constructor(STYLED_STR_CLASS);
let chunks = ffi.chunks.as_mut_slice();
let chunk_count = chunks.len();
let chunk_ptr = chunks.as_mut_ptr();
let chunk_array = env
.new_object_array(
super::array_len(chunk_count),
&styled_chunk_constructor.class,
JObject::null(),
)
.expect("Failed to create chunk array");
for index in 0..chunk_count {
let chunk = unsafe { core::ptr::read(chunk_ptr.add(index)) };
let style = chunk.style;
let text_style = text_style_constructor.new_object(
env,
&[
JValue::Long(style.font as jlong).as_jni(),
JValue::Bool(style.italic).as_jni(),
JValue::Bool(style.underline).as_jni(),
JValue::Bool(style.strikethrough).as_jni(),
JValue::Long(style.foreground as jlong).as_jni(),
JValue::Long(style.background as jlong).as_jni(),
],
);
let text: waterui::Str = unsafe { chunk.text.into_rust() };
let text_jstring = env
.new_string(text.as_str())
.expect("Failed to create text string");
let styled_chunk = styled_chunk_constructor.new_object(
env,
&[
JValue::Object(&text_jstring).as_jni(),
JValue::Object(&text_style).as_jni(),
],
);
chunk_array
.set_element(env, index, &styled_chunk)
.expect("Failed to set chunk array element");
}
ffi.chunks.consume();
styled_str_constructor.new_object(env, &[JValue::Object(&chunk_array).as_jni()])
}
unsafe extern "C" fn watcher_call_date_vec(
data: *mut (),
value: crate::array::WuiArray<crate::components::form::WuiDate>,
metadata_ptr: *mut crate::reactive::WuiWatcherMetadata,
) {
with_watcher_env(data, |env, watcher_data| {
let date_constructor = watcher_data.constructor(DATE_CLASS);
let dates_slice = value.as_slice();
let array = env
.new_object_array(
super::array_len(dates_slice.len()),
&date_constructor.class,
JObject::null(),
)
.expect("Failed to create date watcher array");
for (index, date) in dates_slice.iter().enumerate() {
let java_date = date_constructor.new_object(
env,
&[
JValue::Int(date.year).as_jni(),
JValue::Int(date.month as i32).as_jni(),
JValue::Int(date.day as i32).as_jni(),
],
);
array
.set_element(env, index, java_date)
.expect("Failed to write date watcher array element");
}
value.consume();
let metadata = create_metadata_object(env, watcher_data, metadata_ptr);
invoke_callback(env, &watcher_data.callback, &array, &metadata);
});
}
unsafe extern "C" fn watcher_call_date_time(
data: *mut (),
value: crate::components::form::WuiDateTime,
metadata_ptr: *mut crate::reactive::WuiWatcherMetadata,
) {
with_watcher_env(data, |env, watcher_data| {
let java_value = watcher_data.constructor(DATE_TIME_CLASS).new_object(
env,
&[
JValue::Int(value.year).as_jni(),
JValue::Int(value.month as i32).as_jni(),
JValue::Int(value.day as i32).as_jni(),
JValue::Int(value.hour as i32).as_jni(),
JValue::Int(value.minute as i32).as_jni(),
JValue::Int(value.second as i32).as_jni(),
],
);
let metadata = create_metadata_object(env, watcher_data, metadata_ptr);
invoke_callback(env, &watcher_data.callback, &java_value, &metadata);
});
}
unsafe fn call_jni_int_watcher<T: JniIntValue>(
data: *mut (),
value: T,
metadata_ptr: *mut crate::reactive::WuiWatcherMetadata,
) {
unsafe { watcher_call_int(data, value.into_jint(), metadata_ptr) };
}
unsafe extern "C" fn watcher_call_id(
data: *mut (),
value: WuiId,
metadata_ptr: *mut crate::reactive::WuiWatcherMetadata,
) {
unsafe { call_jni_int_watcher(data, value, metadata_ptr) };
}
unsafe extern "C" fn watcher_call_color_scheme(
data: *mut (),
value: WuiColorScheme,
metadata_ptr: *mut crate::reactive::WuiWatcherMetadata,
) {
unsafe { call_jni_int_watcher(data, value, metadata_ptr) };
}
unsafe extern "C" fn watcher_call_cursor_style(
data: *mut (),
value: WuiCursorStyle,
metadata_ptr: *mut crate::reactive::WuiWatcherMetadata,
) {
unsafe { call_jni_int_watcher(data, value, metadata_ptr) };
}
unsafe extern "C" fn watcher_call_horizontal_alignment(
data: *mut (),
value: WuiHorizontalAlignment,
metadata_ptr: *mut crate::reactive::WuiWatcherMetadata,
) {
unsafe { call_jni_int_watcher(data, value, metadata_ptr) };
}
fn create_watcher_struct_with_call<'local, F>(
env: &mut Env<'local>,
callback: &JObject<'local>,
call_fn: F,
constructors: &[(&'static JNIStr, &'static MethodSignature<'static, 'static>)],
) -> JObject<'local>
where
F: Fn() -> *const (),
{
let jvm = Arc::new(env.get_java_vm().expect("Failed to get JavaVM"));
let global_callback = env
.new_global_ref(callback)
.expect("Failed to create global ref for callback");
let metadata_constructor =
JavaConstructor::load(env, WATCHER_METADATA_CLASS, WATCHER_METADATA_CTOR);
let value_constructors = constructors
.iter()
.map(|&(class_name, descriptor)| {
(
class_name,
JavaConstructor::load(env, class_name, descriptor),
)
})
.collect();
let watcher_data = Box::new(Rc::new(WatcherData {
callback: global_callback,
jvm,
metadata_constructor,
value_constructors,
}));
let data_ptr = Box::into_raw(watcher_data) as jlong;
let call_ptr = call_fn() as jlong;
let drop_ptr = watcher_drop as *const () as jlong;
env.new_object(
WATCHER_STRUCT_CLASS,
WATCHER_STRUCT_CTOR,
&[
JValue::Long(data_ptr),
JValue::Long(call_ptr),
JValue::Long(drop_ptr),
],
)
.expect("Failed to create WatcherStruct")
}
macro_rules! jni_create_watcher_typed {
($name:ident, $call_fn:ident, $constructors:expr) => {
pastey::paste! {
#[unsafe(no_mangle)]
extern "system" fn [<Java_dev_waterui_android_ffi_WatcherJni_create $name Watcher>]<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
callback: JObject<'local>,
) -> jobject {
super::with_env(&mut env, |env| {
create_watcher_struct_with_call(
env,
&callback,
|| $call_fn as *const (),
$constructors,
)
.into_raw()
})
}
}
};
}
jni_create_watcher_typed!(Bool, watcher_call_bool, &[]);
jni_create_watcher_typed!(Int, watcher_call_int, &[]);
jni_create_watcher_typed!(Double, watcher_call_double, &[]);
jni_create_watcher_typed!(Float, watcher_call_float, &[]);
jni_create_watcher_typed!(String, watcher_call_string, &[]);
jni_create_watcher_typed!(Secure, watcher_call_string, &[]);
jni_create_watcher_typed!(Id, watcher_call_id, &[]);
jni_create_watcher_typed!(AnyView, watcher_call_any_view, &[]);
jni_create_watcher_typed!(Color, watcher_call_color, &[]);
jni_create_watcher_typed!(StyledStrPlain, watcher_call_styled_str_plain, &[]);
jni_create_watcher_typed!(
StyledStr,
watcher_call_styled_str,
&[
(TEXT_STYLE_CLASS, TEXT_STYLE_CTOR),
(STYLED_CHUNK_CLASS, STYLED_CHUNK_CTOR),
(STYLED_STR_CLASS, STYLED_STR_CTOR),
]
);
jni_create_watcher_typed!(
ResolvedColor,
watcher_call_resolved_color,
&[(RESOLVED_COLOR_CLASS, RESOLVED_COLOR_CTOR)]
);
jni_create_watcher_typed!(
ResolvedFont,
watcher_call_resolved_font,
&[(RESOLVED_FONT_CLASS, RESOLVED_FONT_CTOR)]
);
jni_create_watcher_typed!(ColorScheme, watcher_call_color_scheme, &[]);
jni_create_watcher_typed!(CursorStyle, watcher_call_cursor_style, &[]);
jni_create_watcher_typed!(HorizontalAlignment, watcher_call_horizontal_alignment, &[]);
jni_create_watcher_typed!(DateVec, watcher_call_date_vec, &[(DATE_CLASS, DATE_CTOR)]);
jni_create_watcher_typed!(
DateTime,
watcher_call_date_time,
&[(DATE_TIME_CLASS, DATE_TIME_CTOR)]
);
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_dynamicConnect<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
dynamic_ptr: jlong,
watcher: JObject<'local>,
) {
let (data_ptr, call_ptr, drop_ptr) = super::with_env(&mut env, |env| {
crate::jni::extract_watcher_struct(env, &watcher)
});
let call_fn: unsafe extern "C" fn(
*mut (),
*mut crate::WuiAnyView,
*mut crate::reactive::WuiWatcherMetadata,
) = unsafe { core::mem::transmute(call_ptr as *const ()) };
let drop_fn: unsafe extern "C" fn(*mut ()) =
unsafe { core::mem::transmute(drop_ptr as *const ()) };
let watcher = unsafe {
crate::reactive::WuiWatcher::<waterui::AnyView>::new(data_ptr as *mut (), call_fn, drop_fn)
};
let watcher_ptr = Box::into_raw(Box::new(watcher));
unsafe {
crate::components::dynamic::waterui_dynamic_connect(
dynamic_ptr as *mut crate::components::dynamic::WuiDynamic,
watcher_ptr,
);
}
}