extern crate alloc;
extern crate std;
use alloc::boxed::Box;
use alloc::vec::Vec;
#[cfg(target_os = "android")]
use core::ffi::c_void;
use jni::objects::{Global, JClass, JObject, JObjectArray, JValue};
#[cfg(target_os = "android")]
use jni::sys::jboolean;
#[cfg(target_os = "android")]
use jni::sys::jdouble;
use jni::sys::{jfloat, jint, jintArray, jlong, jobject, jobjectArray};
use jni::{Env, EnvUnowned, jni_sig, jni_str};
use nami::SignalExt;
use std::sync::Arc;
use waterui_layout::{
HorizontalAlignment, Layout, ProposalSize, Rect, Size, StretchAxis, SubView, VerticalAlignment,
ViewDimensions, measure_layout, with_memoized_children,
};
use crate::IntoFFI;
use crate::components::layout::WuiLayout;
use waterui_graphics::color::ResolvedColor;
use waterui_text::font::{FontWeight, ResolvedFont};
#[cfg(target_os = "android")]
use ndk_sys::ANativeWindow;
#[cfg(target_os = "android")]
struct AndroidNativeWindow {
ptr: *mut ANativeWindow,
}
#[cfg(target_os = "android")]
impl AndroidNativeWindow {
unsafe fn from_surface(env: *mut jni::sys::JNIEnv, surface: jobject) -> Option<Self> {
let ptr = unsafe { ndk_sys::ANativeWindow_fromSurface(env.cast(), surface.cast()) };
(!ptr.is_null()).then_some(Self { ptr })
}
const fn as_void_ptr(&self) -> *mut c_void {
self.ptr.cast::<c_void>()
}
}
#[cfg(target_os = "android")]
impl Drop for AndroidNativeWindow {
fn drop(&mut self) {
unsafe { ndk_sys::ANativeWindow_release(self.ptr) };
}
}
#[cfg(target_os = "android")]
fn require_native_window(
window: Option<AndroidNativeWindow>,
function: &str,
) -> AndroidNativeWindow {
window.unwrap_or_else(|| {
panic!("WatcherJni.{function} failed to obtain ANativeWindow from Surface")
})
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_callAction<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
action_ptr: jlong,
env_ptr: jlong,
) {
unsafe {
crate::action::waterui_call_action(
action_ptr as *const crate::action::WuiAction,
env_ptr as *const crate::WuiEnv,
);
}
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_callSharedAction<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
action_ptr: jlong,
env_ptr: jlong,
) {
unsafe {
crate::waterui_call_shared_action(
action_ptr as *const crate::WuiSharedAction,
env_ptr as *const crate::WuiEnv,
);
}
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_gestureFromPtr<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
gesture_ptr: jlong,
) -> jobject {
use crate::jni::convert::jlong_to_ptr;
let gesture = unsafe { &*jlong_to_ptr::<crate::gesture::WuiGesture>(gesture_ptr) };
super::with_env(&mut env, |env| {
crate::jni::convert::gesture_to_java(env, gesture).into_raw()
})
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_dropGesture<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
gesture_ptr: jlong,
) {
use crate::jni::convert::jlong_to_ptr_mut;
unsafe {
crate::gesture::waterui_drop_gesture(jlong_to_ptr_mut::<crate::gesture::WuiGesture>(
gesture_ptr,
));
}
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_callIndexAction<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
action_ptr: jlong,
env_ptr: jlong,
index: jlong,
) {
let index = usize::try_from(index).expect("list row index must not be negative");
unsafe {
crate::action::waterui_call_index_action(
action_ptr as *const crate::action::WuiIndexAction,
env_ptr as *const crate::WuiEnv,
index,
);
}
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_callMoveAction<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
action_ptr: jlong,
env_ptr: jlong,
from_index: jlong,
to_index: jlong,
) {
let from_index = usize::try_from(from_index).expect("list move source must not be negative");
let to_index = usize::try_from(to_index).expect("list move target must not be negative");
unsafe {
crate::action::waterui_call_move_action(
action_ptr as *const crate::action::WuiMoveAction,
env_ptr as *const crate::WuiEnv,
from_index,
to_index,
);
}
}
struct JniSubView {
jvm: Arc<jni::JavaVM>,
subview_ref: Global<JObject<'static>>,
stretch_axis: StretchAxis,
priority: i32,
}
impl SubView for JniSubView {
fn measure(&self, proposal: ProposalSize) -> ViewDimensions {
super::with_attached_env(&self.jvm, |env| {
let width = proposal.width.unwrap_or(f32::NAN);
let height = proposal.height.unwrap_or(f32::NAN);
let dimensions_obj = env
.call_method(
&self.subview_ref,
jni_str!("measureForLayout"),
jni_sig!("(FF)Ldev/waterui/android/runtime/ViewDimensionsStruct;"),
&[JValue::Float(width), JValue::Float(height)],
)
.expect("measureForLayout call failed")
.l()
.expect("measureForLayout should return object");
extract_view_dimensions(env, &dimensions_obj)
})
.expect("Failed to attach to JVM")
}
fn stretch_axis(&self) -> StretchAxis {
self.stretch_axis
}
fn priority(&self) -> i32 {
self.priority
}
}
fn horizontal_alignment_to_java<'local>(
env: &mut Env<'local>,
alignment: HorizontalAlignment,
) -> JObject<'local> {
let class = env
.find_class(jni_str!("dev/waterui/android/runtime/HorizontalAlignment"))
.expect("HorizontalAlignment class");
let field_name = if alignment == HorizontalAlignment::Leading {
jni_str!("LEADING")
} else if alignment == HorizontalAlignment::Trailing {
jni_str!("TRAILING")
} else {
jni_str!("CENTER")
};
env.get_static_field(
&class,
field_name,
jni_sig!("Ldev/waterui/android/runtime/HorizontalAlignment;"),
)
.expect("HorizontalAlignment enum value")
.l()
.expect("horizontal alignment is object")
}
fn vertical_alignment_to_java<'local>(
env: &mut Env<'local>,
alignment: VerticalAlignment,
) -> JObject<'local> {
let class = env
.find_class(jni_str!("dev/waterui/android/runtime/VerticalAlignment"))
.expect("VerticalAlignment class");
let field_name = if alignment == VerticalAlignment::Top {
jni_str!("TOP")
} else if alignment == VerticalAlignment::Bottom {
jni_str!("BOTTOM")
} else if alignment == VerticalAlignment::FirstBaseline {
jni_str!("FIRST_BASELINE")
} else if alignment == VerticalAlignment::LastBaseline {
jni_str!("LAST_BASELINE")
} else {
jni_str!("CENTER")
};
env.get_static_field(
&class,
field_name,
jni_sig!("Ldev/waterui/android/runtime/VerticalAlignment;"),
)
.expect("VerticalAlignment enum value")
.l()
.expect("vertical alignment is object")
}
fn extract_horizontal_alignment(env: &mut Env, alignment: &JObject) -> HorizontalAlignment {
let ordinal = env
.call_method(alignment, jni_str!("ordinal"), jni_sig!("()I"), &[])
.expect("horizontal alignment ordinal()")
.i()
.expect("horizontal alignment ordinal is int");
match ordinal {
0 => HorizontalAlignment::Leading,
1 => HorizontalAlignment::Center,
2 => HorizontalAlignment::Trailing,
_ => panic!("unknown HorizontalAlignment ordinal {ordinal}"),
}
}
fn extract_vertical_alignment(env: &mut Env, alignment: &JObject) -> VerticalAlignment {
let ordinal = env
.call_method(alignment, jni_str!("ordinal"), jni_sig!("()I"), &[])
.expect("vertical alignment ordinal()")
.i()
.expect("vertical alignment ordinal is int");
match ordinal {
0 => VerticalAlignment::Top,
1 => VerticalAlignment::Center,
2 => VerticalAlignment::Bottom,
3 => VerticalAlignment::FirstBaseline,
4 => VerticalAlignment::LastBaseline,
_ => panic!("unknown VerticalAlignment ordinal {ordinal}"),
}
}
fn size_to_java<'local>(env: &mut Env<'local>, size: Size) -> JObject<'local> {
let class = env
.find_class(jni_str!("dev/waterui/android/runtime/SizeStruct"))
.expect("SizeStruct class");
env.new_object(
&class,
jni_sig!("(FF)V"),
&[JValue::Float(size.width), JValue::Float(size.height)],
)
.expect("create SizeStruct")
}
fn horizontal_guide_to_java<'local>(
env: &mut Env<'local>,
alignment: HorizontalAlignment,
value: f32,
) -> JObject<'local> {
let class = env
.find_class(jni_str!(
"dev/waterui/android/runtime/HorizontalGuideStruct"
))
.expect("HorizontalGuideStruct class");
let alignment = horizontal_alignment_to_java(env, alignment);
env.new_object(
&class,
jni_sig!("(Ldev/waterui/android/runtime/HorizontalAlignment;F)V"),
&[JValue::Object(&alignment), JValue::Float(value)],
)
.expect("create HorizontalGuideStruct")
}
fn vertical_guide_to_java<'local>(
env: &mut Env<'local>,
alignment: VerticalAlignment,
value: f32,
) -> JObject<'local> {
let class = env
.find_class(jni_str!("dev/waterui/android/runtime/VerticalGuideStruct"))
.expect("VerticalGuideStruct class");
let alignment = vertical_alignment_to_java(env, alignment);
env.new_object(
&class,
jni_sig!("(Ldev/waterui/android/runtime/VerticalAlignment;F)V"),
&[JValue::Object(&alignment), JValue::Float(value)],
)
.expect("create VerticalGuideStruct")
}
fn view_dimensions_to_java<'local>(
env: &mut Env<'local>,
dimensions: &ViewDimensions,
) -> JObject<'local> {
let size = size_to_java(env, dimensions.size);
let horizontal_class = env
.find_class(jni_str!(
"dev/waterui/android/runtime/HorizontalGuideStruct"
))
.expect("HorizontalGuideStruct class");
let horizontal_guides: Vec<_> = dimensions
.explicit_horizontal_guides()
.map(|(alignment, value)| horizontal_guide_to_java(env, alignment, value))
.collect();
let horizontal_array = env
.new_object_array(
super::array_len(horizontal_guides.len()),
&horizontal_class,
JObject::null(),
)
.expect("create HorizontalGuideStruct array");
for (index, guide) in horizontal_guides.iter().enumerate() {
horizontal_array
.set_element(env, index, guide)
.expect("set horizontal guide element");
}
let vertical_class = env
.find_class(jni_str!("dev/waterui/android/runtime/VerticalGuideStruct"))
.expect("VerticalGuideStruct class");
let vertical_guides: Vec<_> = dimensions
.explicit_vertical_guides()
.map(|(alignment, value)| vertical_guide_to_java(env, alignment, value))
.collect();
let vertical_array = env
.new_object_array(
super::array_len(vertical_guides.len()),
&vertical_class,
JObject::null(),
)
.expect("create VerticalGuideStruct array");
for (index, guide) in vertical_guides.iter().enumerate() {
vertical_array
.set_element(env, index, guide)
.expect("set vertical guide element");
}
let dimensions_class = env
.find_class(jni_str!("dev/waterui/android/runtime/ViewDimensionsStruct"))
.expect("ViewDimensionsStruct class");
env.new_object(&dimensions_class, jni_sig!("(Ldev/waterui/android/runtime/SizeStruct;[Ldev/waterui/android/runtime/HorizontalGuideStruct;[Ldev/waterui/android/runtime/VerticalGuideStruct;)V"), &[
JValue::Object(&size),
JValue::Object(&JObject::from(horizontal_array)),
JValue::Object(&JObject::from(vertical_array)),
],)
.expect("create ViewDimensionsStruct")
}
fn extract_view_dimensions(env: &mut Env, dimensions: &JObject) -> ViewDimensions {
let size_obj = env
.get_field(
dimensions,
jni_str!("size"),
jni_sig!("Ldev/waterui/android/runtime/SizeStruct;"),
)
.expect("ViewDimensionsStruct.size")
.l()
.expect("size is object");
let width = env
.get_field(&size_obj, jni_str!("width"), jni_sig!("F"))
.expect("SizeStruct.width")
.f()
.expect("width is float");
let height = env
.get_field(&size_obj, jni_str!("height"), jni_sig!("F"))
.expect("SizeStruct.height")
.f()
.expect("height is float");
let mut result = ViewDimensions::new(Size { width, height });
let horizontal_array = env
.get_field(
dimensions,
jni_str!("horizontalGuides"),
jni_sig!("[Ldev/waterui/android/runtime/HorizontalGuideStruct;"),
)
.expect("ViewDimensionsStruct.horizontalGuides")
.l()
.expect("horizontalGuides is object");
let horizontal_array = env
.cast_local::<JObjectArray>(horizontal_array)
.expect("horizontalGuides must be an object array");
let horizontal_len = horizontal_array
.len(env)
.expect("horizontal guide array length");
for index in 0..horizontal_len {
let guide = horizontal_array
.get_element(env, index)
.expect("horizontal guide element");
let alignment_obj = env
.get_field(
&guide,
jni_str!("alignment"),
jni_sig!("Ldev/waterui/android/runtime/HorizontalAlignment;"),
)
.expect("HorizontalGuideStruct.alignment")
.l()
.expect("alignment is object");
let value = env
.get_field(&guide, jni_str!("value"), jni_sig!("F"))
.expect("HorizontalGuideStruct.value")
.f()
.expect("value is float");
result.set_horizontal(extract_horizontal_alignment(env, &alignment_obj), value);
}
let vertical_array = env
.get_field(
dimensions,
jni_str!("verticalGuides"),
jni_sig!("[Ldev/waterui/android/runtime/VerticalGuideStruct;"),
)
.expect("ViewDimensionsStruct.verticalGuides")
.l()
.expect("verticalGuides is object");
let vertical_array = env
.cast_local::<JObjectArray>(vertical_array)
.expect("verticalGuides must be an object array");
let vertical_len = vertical_array
.len(env)
.expect("vertical guide array length");
for index in 0..vertical_len {
let guide = vertical_array
.get_element(env, index)
.expect("vertical guide element");
let alignment_obj = env
.get_field(
&guide,
jni_str!("alignment"),
jni_sig!("Ldev/waterui/android/runtime/VerticalAlignment;"),
)
.expect("VerticalGuideStruct.alignment")
.l()
.expect("alignment is object");
let value = env
.get_field(&guide, jni_str!("value"), jni_sig!("F"))
.expect("VerticalGuideStruct.value")
.f()
.expect("value is float");
result.set_vertical(extract_vertical_alignment(env, &alignment_obj), value);
}
result
}
fn extract_proposal(env: &mut Env, proposal: &JObject) -> ProposalSize {
let width = env
.get_field(proposal, jni_str!("width"), jni_sig!("F"))
.expect("ProposalStruct.width")
.f()
.expect("width is float");
let height = env
.get_field(proposal, jni_str!("height"), jni_sig!("F"))
.expect("ProposalStruct.height")
.f()
.expect("height is float");
ProposalSize {
width: if width.is_finite() { Some(width) } else { None },
height: if height.is_finite() {
Some(height)
} else {
None
},
}
}
fn extract_rect(env: &mut Env, rect: &JObject) -> Rect {
let x = env
.get_field(rect, jni_str!("x"), jni_sig!("F"))
.expect("RectStruct.x")
.f()
.expect("x is float");
let y = env
.get_field(rect, jni_str!("y"), jni_sig!("F"))
.expect("RectStruct.y")
.f()
.expect("y is float");
let width = env
.get_field(rect, jni_str!("width"), jni_sig!("F"))
.expect("RectStruct.width")
.f()
.expect("width is float");
let height = env
.get_field(rect, jni_str!("height"), jni_sig!("F"))
.expect("RectStruct.height")
.f()
.expect("height is float");
Rect::new(waterui_layout::Point { x, y }, Size { width, height })
}
fn stretch_axis_from_ordinal(ordinal: i32) -> StretchAxis {
match ordinal {
0 => StretchAxis::None,
1 => StretchAxis::Horizontal,
2 => StretchAxis::Vertical,
3 => StretchAxis::Both,
4 => StretchAxis::MainAxis,
5 => StretchAxis::CrossAxis,
value => panic!("unknown StretchAxis ordinal: {value}"),
}
}
fn extract_subviews(env: &mut Env, subviews_array: jobjectArray) -> Vec<JniSubView> {
let array_obj = unsafe { JObjectArray::<JObject>::from_raw(env, subviews_array) };
let len = array_obj.len(env).expect("get array length");
let mut result = Vec::with_capacity(len);
let jvm = Arc::new(
env.get_java_vm()
.expect("SubView extraction failed to access JavaVM"),
);
for i in 0..len {
let subview_obj = array_obj.get_element(env, i).expect("get array element");
let stretch_axis_obj = env
.get_field(
&subview_obj,
jni_str!("stretchAxis"),
jni_sig!("Ldev/waterui/android/runtime/StretchAxis;"),
)
.expect("SubViewStruct.stretchAxis")
.l()
.expect("stretchAxis is object");
let stretch_ordinal = env
.call_method(&stretch_axis_obj, jni_str!("ordinal"), jni_sig!("()I"), &[])
.expect("ordinal()")
.i()
.expect("ordinal is int");
let priority = env
.get_field(&subview_obj, jni_str!("priority"), jni_sig!("I"))
.expect("SubViewStruct.priority")
.i()
.expect("priority is int");
let global_ref = env.new_global_ref(&subview_obj).expect("create global ref");
result.push(JniSubView {
jvm: Arc::clone(&jvm),
subview_ref: global_ref,
stretch_axis: stretch_axis_from_ordinal(stretch_ordinal),
priority,
});
}
result
}
#[cfg(target_os = "android")]
struct AndroidLayoutInvalidationTarget {
jvm: jni::JavaVM,
owner: Global<JObject<'static>>,
}
#[cfg(target_os = "android")]
unsafe extern "C" fn invalidate_android_layout(context: *mut c_void) {
let target = unsafe { &*context.cast::<AndroidLayoutInvalidationTarget>() };
super::with_attached_env(&target.jvm, |env| {
env.call_method(
&target.owner,
jni_str!("requestLayout"),
jni_sig!("()V"),
&[],
)
.expect("layout invalidation failed to request native layout");
})
.expect("layout invalidation failed to attach to JVM");
}
#[cfg(target_os = "android")]
unsafe extern "C" fn drop_android_layout_invalidation_target(context: *mut c_void) {
unsafe {
drop(Box::from_raw(
context.cast::<AndroidLayoutInvalidationTarget>(),
));
}
}
#[cfg(target_os = "android")]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_layoutWatchInvalidation<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
layout_ptr: jlong,
owner: JObject<'local>,
) -> jlong {
super::with_env(&mut env, |env| {
let target = Box::new(AndroidLayoutInvalidationTarget {
jvm: env
.get_java_vm()
.expect("layoutWatchInvalidation failed to access JavaVM"),
owner: env
.new_global_ref(owner)
.expect("layoutWatchInvalidation failed to retain owner view"),
});
let context = Box::into_raw(target).cast::<c_void>();
unsafe {
crate::components::layout::waterui_layout_watch_invalidation(
layout_ptr as *const WuiLayout,
context,
invalidate_android_layout,
drop_android_layout_invalidation_target,
) as jlong
}
})
}
#[cfg(target_os = "android")]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_layoutWatcherDrop<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
watcher_ptr: jlong,
) {
unsafe {
crate::components::layout::waterui_layout_watcher_drop(
watcher_ptr as *mut crate::components::layout::WuiLayoutWatcher,
);
}
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_layoutMeasure<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
layout_ptr: jlong,
proposal: JObject<'local>,
subviews: jobjectArray,
) -> jobject {
super::with_env(&mut env, |env| {
let layout: &dyn Layout = unsafe { &*(*(layout_ptr as *mut WuiLayout)).0 };
let proposal = extract_proposal(env, &proposal);
let jni_subviews = extract_subviews(env, subviews);
let subview_refs: Vec<&dyn SubView> =
jni_subviews.iter().map(|s| s as &dyn SubView).collect();
let dimensions = measure_layout(layout, proposal, &subview_refs);
view_dimensions_to_java(env, &dimensions).into_raw()
})
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_layoutSizeThatFits<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
layout_ptr: jlong,
proposal: JObject<'local>,
subviews: jobjectArray,
) -> jobject {
super::with_env(&mut env, |env| {
let layout: &dyn Layout = unsafe { &*(*(layout_ptr as *mut WuiLayout)).0 };
let proposal = extract_proposal(env, &proposal);
let jni_subviews = extract_subviews(env, subviews);
let subview_refs: Vec<&dyn SubView> =
jni_subviews.iter().map(|s| s as &dyn SubView).collect();
let size =
with_memoized_children(&subview_refs, |refs| layout.size_that_fits(proposal, refs));
env.new_object(
jni_str!("dev/waterui/android/runtime/SizeStruct"),
jni_sig!("(FF)V"),
&[JValue::Float(size.width), JValue::Float(size.height)],
)
.expect("create SizeStruct")
.into_raw()
})
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_layoutPlace<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
layout_ptr: jlong,
bounds: JObject<'local>,
subviews: jobjectArray,
) -> jobjectArray {
super::with_env(&mut env, |env| {
let layout: &dyn Layout = unsafe { &*(*(layout_ptr as *mut WuiLayout)).0 };
let bounds = extract_rect(env, &bounds);
let jni_subviews = extract_subviews(env, subviews);
let subview_refs: Vec<&dyn SubView> =
jni_subviews.iter().map(|s| s as &dyn SubView).collect();
let rects = with_memoized_children(&subview_refs, |refs| layout.place(bounds, refs));
let rect_class = env
.find_class(jni_str!("dev/waterui/android/runtime/RectStruct"))
.expect("RectStruct class");
let result_array = env
.new_object_array(super::array_len(rects.len()), &rect_class, JObject::null())
.expect("create RectStruct array");
for (i, rect) in rects.iter().enumerate() {
let rect_obj = env
.new_object(
&rect_class,
jni_sig!("(FFFF)V"),
&[
JValue::Float(rect.x()),
JValue::Float(rect.y()),
JValue::Float(rect.width()),
JValue::Float(rect.height()),
],
)
.expect("create RectStruct");
result_array
.set_element(env, i, rect_obj)
.expect("set array element");
}
result_array.into_raw()
})
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_layoutLazyStackAxis<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
layout_ptr: jlong,
) -> jint {
unsafe {
crate::components::layout::waterui_layout_lazy_stack_axis(layout_ptr as *mut WuiLayout)
as jint
}
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_layoutLazyStackSpacing<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
layout_ptr: jlong,
) -> jfloat {
unsafe {
crate::components::layout::waterui_layout_lazy_stack_spacing(layout_ptr as *mut WuiLayout)
}
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_layoutLazyStackHorizontalAlignment<
'local,
>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
layout_ptr: jlong,
) -> jint {
unsafe {
crate::components::layout::waterui_layout_lazy_stack_horizontal_alignment(
layout_ptr as *mut WuiLayout,
) as jint
}
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_layoutLazyStackVerticalAlignment<
'local,
>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
layout_ptr: jlong,
) -> jint {
unsafe {
crate::components::layout::waterui_layout_lazy_stack_vertical_alignment(
layout_ptr as *mut WuiLayout,
) as jint
}
}
fn color_slot(slot: jint) -> crate::theme::WuiColorSlot {
match slot {
0 => crate::theme::WuiColorSlot::Background,
1 => crate::theme::WuiColorSlot::Surface,
2 => crate::theme::WuiColorSlot::SurfaceVariant,
3 => crate::theme::WuiColorSlot::Border,
4 => crate::theme::WuiColorSlot::Foreground,
5 => crate::theme::WuiColorSlot::MutedForeground,
6 => crate::theme::WuiColorSlot::Accent,
7 => crate::theme::WuiColorSlot::AccentForeground,
8 => crate::theme::WuiColorSlot::AccentContainer,
9 => crate::theme::WuiColorSlot::Tertiary,
10 => crate::theme::WuiColorSlot::TertiaryContainer,
11 => crate::theme::WuiColorSlot::SelectionContainer,
12 => crate::theme::WuiColorSlot::SelectionForeground,
value => panic!("unknown color slot ordinal: {value}"),
}
}
fn font_slot(slot: jint) -> crate::theme::WuiFontSlot {
match slot {
0 => crate::theme::WuiFontSlot::Body,
1 => crate::theme::WuiFontSlot::Title,
2 => crate::theme::WuiFontSlot::Headline,
3 => crate::theme::WuiFontSlot::Subheadline,
4 => crate::theme::WuiFontSlot::Caption,
5 => crate::theme::WuiFontSlot::Footnote,
value => panic!("unknown font slot ordinal: {value}"),
}
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_themeInstallColor<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
env_ptr: jlong,
slot: jint,
signal_ptr: jlong,
) {
let slot = color_slot(slot);
unsafe {
crate::theme::waterui_theme_install_color(
env_ptr as *mut crate::WuiEnv,
slot,
signal_ptr as *mut crate::reactive::WuiComputed<waterui_graphics::color::ResolvedColor>,
);
}
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_themeInstallFont<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
env_ptr: jlong,
slot: jint,
signal_ptr: jlong,
) {
let slot = font_slot(slot);
unsafe {
crate::theme::waterui_theme_install_font(
env_ptr as *mut crate::WuiEnv,
slot,
signal_ptr as *mut crate::reactive::WuiComputed<waterui_text::font::ResolvedFont>,
);
}
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_themeInstallColorScheme<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
env_ptr: jlong,
signal_ptr: jlong,
) {
unsafe {
crate::theme::waterui_theme_install_color_scheme(
env_ptr as *mut crate::WuiEnv,
signal_ptr as *mut crate::reactive::WuiComputed<waterui::theme::ColorScheme>,
);
}
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_themeColor<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
env_ptr: jlong,
slot: jint,
) -> jlong {
let slot = color_slot(slot);
unsafe { crate::theme::waterui_theme_color(env_ptr as *const crate::WuiEnv, slot) as jlong }
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_themeFont<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
env_ptr: jlong,
slot: jint,
) -> jlong {
let slot = font_slot(slot);
unsafe { crate::theme::waterui_theme_font(env_ptr as *const crate::WuiEnv, slot) as jlong }
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_envDisabled<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
env_ptr: jlong,
) -> jlong {
unsafe { crate::waterui_env_disabled(env_ptr as *const crate::WuiEnv) as jlong }
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_themeColorScheme<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
env_ptr: jlong,
) -> jlong {
unsafe { crate::theme::waterui_theme_color_scheme(env_ptr as *const crate::WuiEnv) as jlong }
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_getAnimationKindDurationPacked<
'local,
>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
metadata_ptr: jlong,
) -> jlong {
let animation = unsafe {
crate::animation::waterui_get_animation(
metadata_ptr as *const crate::reactive::WuiWatcherMetadata,
)
};
let (tag, duration_ms): (u32, u32) = match animation {
crate::animation::WuiAnimation::None => (0, 0),
crate::animation::WuiAnimation::Bezier { duration_ms, .. } => (
1,
u32::try_from(duration_ms)
.expect("Android animation duration exceeds packed JNI range"),
),
crate::animation::WuiAnimation::Spring { .. } => (2, 0),
};
((u64::from(duration_ms) << 32) | u64::from(tag)).cast_signed()
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_getAnimationParams12Packed<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
metadata_ptr: jlong,
) -> jlong {
let animation = unsafe {
crate::animation::waterui_get_animation(
metadata_ptr as *const crate::reactive::WuiWatcherMetadata,
)
};
let (p1, p2): (f32, f32) = match animation {
crate::animation::WuiAnimation::Bezier { x1, y1, .. } => (x1, y1),
crate::animation::WuiAnimation::Spring { stiffness, damping } => (stiffness, damping),
crate::animation::WuiAnimation::None => (0.0, 0.0),
};
((u64::from(p2.to_bits()) << 32) | u64::from(p1.to_bits())).cast_signed()
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_getAnimationParams34Packed<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
metadata_ptr: jlong,
) -> jlong {
let animation = unsafe {
crate::animation::waterui_get_animation(
metadata_ptr as *const crate::reactive::WuiWatcherMetadata,
)
};
let (p3, p4): (f32, f32) = match animation {
crate::animation::WuiAnimation::Bezier { x2, y2, .. } => (x2, y2),
crate::animation::WuiAnimation::Spring { .. } | crate::animation::WuiAnimation::None => {
(0.0, 0.0)
}
};
((u64::from(p4.to_bits()) << 32) | u64::from(p3.to_bits())).cast_signed()
}
struct AnyViewsWatchData {
jvm: jni::JavaVM,
callback: Global<JObject<'static>>,
}
unsafe extern "C" fn anyviews_watch_call(
data: *mut (),
ids: crate::array::WuiArray<crate::id::WuiId>,
metadata_ptr: *mut crate::reactive::WuiWatcherMetadata,
) {
drop(unsafe { Box::from_raw(metadata_ptr) });
let values: Vec<jint> = ids.iter().map(|id| id.inner).collect();
ids.consume();
let watcher_data = unsafe { &*data.cast::<AnyViewsWatchData>() };
super::with_attached_env(&watcher_data.jvm, |env| {
let ids = env
.new_int_array(values.len())
.expect("anyviews_watch_call: failed to allocate callback IDs");
ids.set_region(env, 0, &values)
.expect("anyviews_watch_call: failed to write callback IDs");
let ids = JObject::from(ids);
env.call_method(
&watcher_data.callback,
jni_str!("onChanged"),
jni_sig!("([I)V"),
&[JValue::Object(&ids)],
)
.expect("anyviews_watch_call: NativeAnyViewsWatcher.onChanged failed");
})
.expect("anyviews_watch_call: failed to attach current thread to JVM");
}
unsafe extern "C" fn anyviews_watch_drop(data: *mut ()) {
unsafe {
let _: Box<AnyViewsWatchData> = Box::from_raw(data.cast::<AnyViewsWatchData>());
}
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_anyViewsLen<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
handle: jlong,
) -> jint {
let len =
unsafe { crate::views::waterui_anyviews_len(handle as *const crate::views::WuiAnyViews) };
jint::try_from(len).expect("view collection length exceeds jint capacity")
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_anyViewsGetIdsInRange<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
handle: jlong,
start: jint,
end: jint,
) -> jintArray {
super::with_env(&mut env, |env| {
let start = usize::try_from(start).expect("id range start must not be negative");
let end = usize::try_from(end).expect("id range end must not be negative");
let ids = unsafe {
crate::views::waterui_anyviews_get_ids_in_range(
handle as *const crate::views::WuiAnyViews,
start,
end,
)
};
let rust_ids: Vec<waterui_core::id::Id> = unsafe { crate::IntoRust::into_rust(ids) };
let values: Vec<jint> = rust_ids.into_iter().map(i32::from).collect();
let array = env
.new_int_array(values.len())
.expect("anyViewsGetIdsInRange: failed to allocate jintArray");
array
.set_region(env, 0, &values)
.expect("anyViewsGetIdsInRange: failed to write ids into jintArray");
array.into_raw()
})
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_anyViewsGetView<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
handle: jlong,
index: jint,
) -> jlong {
let index = usize::try_from(index).expect("view collection index must not be negative");
unsafe {
crate::views::waterui_anyviews_get_view(handle as *const crate::views::WuiAnyViews, index)
as jlong
}
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_anyViewsWatch<'local>(
env: EnvUnowned<'local>,
_class: JClass<'local>,
handle: jlong,
callback: JObject<'local>,
) -> jlong {
any_views_watch_range(env, handle, 0, jint::MAX, &callback)
}
fn any_views_watch_range<'local>(
mut env: EnvUnowned<'local>,
handle: jlong,
start: jint,
end: jint,
callback: &JObject<'local>,
) -> jlong {
super::with_env(&mut env, |env| {
let jvm = env
.get_java_vm()
.expect("WatcherJni.anyViewsWatchRange failed to access JavaVM");
let callback = env
.new_global_ref(callback)
.expect("WatcherJni.anyViewsWatchRange failed to create callback global reference");
let data = Box::new(AnyViewsWatchData { jvm, callback });
let data_ptr = Box::into_raw(data).cast::<()>();
let start = usize::try_from(start).expect("watch range start must not be negative");
let end = usize::try_from(end).expect("watch range end must not be negative");
let watcher = unsafe {
crate::views::waterui_anyviews_watch_range(
handle as *const crate::views::WuiAnyViews,
start,
end,
data_ptr,
anyviews_watch_call,
anyviews_watch_drop,
)
};
watcher as jlong
})
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_colorFromLinearRgbaHeadroom<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
red: jfloat,
green: jfloat,
blue: jfloat,
alpha: jfloat,
headroom: jfloat,
) -> jlong {
unsafe {
crate::color::waterui_color_from_linear_rgba_headroom(red, green, blue, alpha, headroom)
as jlong
}
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_resolveColor<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
color_ptr: jlong,
env_ptr: jlong,
) -> jlong {
unsafe {
crate::color::waterui_resolve_color(
color_ptr as *const crate::color::WuiColor,
env_ptr as *const crate::WuiEnv,
) as jlong
}
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_resolveFont<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
font_ptr: jlong,
env_ptr: jlong,
) -> jlong {
unsafe {
crate::components::text::waterui_resolve_font(
font_ptr as *const crate::components::text::WuiFont,
env_ptr as *const crate::WuiEnv,
) as jlong
}
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_callLifecycleHook<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
handler_ptr: jlong,
env_ptr: jlong,
) {
unsafe {
crate::event::waterui_call_lifecycle_hook(
handler_ptr as *mut crate::event::WuiLifecycleHookHandler,
env_ptr as *const crate::WuiEnv,
);
}
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_dropLifecycleHook<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
handler_ptr: jlong,
) {
unsafe {
crate::event::waterui_drop_lifecycle_hook(
handler_ptr as *mut crate::event::WuiLifecycleHookHandler,
);
}
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_callOnEvent<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
handler_ptr: jlong,
env_ptr: jlong,
) {
unsafe {
crate::event::waterui_call_on_event(
handler_ptr as *const crate::event::WuiOnEventHandler,
env_ptr as *const crate::WuiEnv,
);
}
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_callOnHoverEvent<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
handler_ptr: jlong,
env_ptr: jlong,
x: jfloat,
y: jfloat,
) {
unsafe {
crate::event::waterui_call_on_hover_event(
handler_ptr as *const crate::event::WuiOnEventHandler,
env_ptr as *const crate::WuiEnv,
x,
y,
);
}
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_dropOnEvent<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
handler_ptr: jlong,
) {
unsafe {
crate::event::waterui_drop_on_event(handler_ptr as *mut crate::event::WuiOnEventHandler);
}
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_dropRetain<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
retain_ptr: jlong,
) {
unsafe { crate::waterui_drop_retain(crate::WuiRetain::from_ptr(retain_ptr as *mut ())) };
}
struct ReactiveColorSchemeState {
binding: waterui::Binding<waterui::theme::ColorScheme>,
}
struct ReactiveColorState {
binding: waterui::Binding<ResolvedColor>,
}
struct ReactiveFontState {
binding: waterui::Binding<ResolvedFont>,
}
struct ReactiveEdgeInsetsState {
binding: waterui::Binding<waterui_layout::padding::EdgeInsets>,
}
#[inline]
const unsafe fn reactive_state<'a, T>(state_ptr: jlong) -> &'a T {
unsafe { crate::borrow_ffi(state_ptr as *const T) }
}
fn color_scheme_from_ordinal(ordinal: jint) -> waterui::theme::ColorScheme {
match ordinal {
0 => waterui::theme::ColorScheme::Light,
1 => waterui::theme::ColorScheme::Dark,
value => panic!("unknown color scheme ordinal: {value}"),
}
}
fn resolved_color_from_argb(argb: jint) -> ResolvedColor {
let argb = argb.cast_unsigned();
let [a, r, g, b] = argb.to_be_bytes();
let srgb = waterui_graphics::color::Srgb::new_u8(r, g, b);
let mut resolved = ResolvedColor::from_srgb(srgb);
resolved.opacity = f32::from(a) / 255.0;
resolved
}
fn font_weight_from_ordinal(weight: jint) -> FontWeight {
match weight {
0 => FontWeight::Thin,
1 => FontWeight::UltraLight,
2 => FontWeight::Light,
3 => FontWeight::Normal,
4 => FontWeight::Medium,
5 => FontWeight::SemiBold,
6 => FontWeight::Bold,
7 => FontWeight::UltraBold,
8 => FontWeight::Black,
value => panic!("unknown font weight ordinal: {value}"),
}
}
fn resolved_system_font(size: jfloat, weight: jint) -> ResolvedFont {
ResolvedFont::new(size, font_weight_from_ordinal(weight))
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_createReactiveColorSchemeState<
'local,
>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
scheme: jint,
) -> jlong {
let state = ReactiveColorSchemeState {
binding: waterui::reactive::binding(color_scheme_from_ordinal(scheme)),
};
Box::into_raw(Box::new(state)) as jlong
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_reactiveColorSchemeStateToComputed<
'local,
>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
) -> jlong {
let state = unsafe { reactive_state::<ReactiveColorSchemeState>(state_ptr) };
let computed = state.binding.computed();
computed.into_ffi() as jlong
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_reactiveColorSchemeStateSet<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
scheme: jint,
) {
let state = unsafe { reactive_state::<ReactiveColorSchemeState>(state_ptr) };
state.binding.set(color_scheme_from_ordinal(scheme));
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_dropReactiveColorSchemeState<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
) {
unsafe { drop(Box::from_raw(state_ptr as *mut ReactiveColorSchemeState)) };
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_createReactiveEdgeInsetsState<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
top: jfloat,
bottom: jfloat,
leading: jfloat,
trailing: jfloat,
) -> jlong {
let state = ReactiveEdgeInsetsState {
binding: waterui::reactive::binding(waterui_layout::padding::EdgeInsets::new(
top, bottom, leading, trailing,
)),
};
Box::into_raw(Box::new(state)) as jlong
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_reactiveEdgeInsetsStateToComputed<
'local,
>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
) -> jlong {
let state = unsafe { reactive_state::<ReactiveEdgeInsetsState>(state_ptr) };
let computed = state.binding.computed();
computed.into_ffi() as jlong
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_reactiveEdgeInsetsStateSet<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
top: jfloat,
bottom: jfloat,
leading: jfloat,
trailing: jfloat,
) {
let state = unsafe { reactive_state::<ReactiveEdgeInsetsState>(state_ptr) };
state.binding.set(waterui_layout::padding::EdgeInsets::new(
top, bottom, leading, trailing,
));
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_dropReactiveEdgeInsetsState<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
) {
unsafe { drop(Box::from_raw(state_ptr as *mut ReactiveEdgeInsetsState)) };
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_envInstallSafeArea<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
env_ptr: jlong,
signal_ptr: jlong,
) {
unsafe {
crate::runtime::safe_area::waterui_env_install_safe_area(
env_ptr as *mut crate::WuiEnv,
signal_ptr as *mut crate::reactive::WuiComputed<waterui_layout::padding::EdgeInsets>,
);
}
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_createReactiveColorState<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
argb: jint,
) -> jlong {
let state = ReactiveColorState {
binding: waterui::reactive::binding(resolved_color_from_argb(argb)),
};
Box::into_raw(Box::new(state)) as jlong
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_reactiveColorStateToComputed<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
) -> jlong {
let state = unsafe { reactive_state::<ReactiveColorState>(state_ptr) };
let computed = state.binding.computed();
computed.into_ffi() as jlong
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_reactiveColorStateSet<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
argb: jint,
) {
let state = unsafe { reactive_state::<ReactiveColorState>(state_ptr) };
state.binding.set(resolved_color_from_argb(argb));
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_dropReactiveColorState<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
) {
unsafe { drop(Box::from_raw(state_ptr as *mut ReactiveColorState)) };
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_createReactiveFontState<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
size: jfloat,
weight: jint,
) -> jlong {
let state = ReactiveFontState {
binding: waterui::reactive::binding(resolved_system_font(size, weight)),
};
Box::into_raw(Box::new(state)) as jlong
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_reactiveFontStateToComputed<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
) -> jlong {
let state = unsafe { reactive_state::<ReactiveFontState>(state_ptr) };
state.binding.computed().into_ffi() as jlong
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_reactiveFontStateSet<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
size: jfloat,
weight: jint,
) {
let state = unsafe { reactive_state::<ReactiveFontState>(state_ptr) };
state.binding.set(resolved_system_font(size, weight));
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_dropReactiveFontState<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
) {
unsafe { drop(Box::from_raw(state_ptr as *mut ReactiveFontState)) };
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_webviewNativeHandle<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
webview_ptr: jlong,
) -> jlong {
unsafe {
crate::components::webview::waterui_webview_native_handle(
webview_ptr as *mut crate::components::webview::WuiWebView,
) as jlong
}
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_webviewNativeView<'local>(
mut jni_env: EnvUnowned<'local>,
_class: JClass<'local>,
handle_ptr: jlong,
) -> jobject {
super::with_env(&mut jni_env, |env| {
crate::jni::webview_bridge::webview_native_view(env, handle_ptr)
})
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_androidVideoSurfaceHostAttach<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
bridge_ptr: jlong,
host: JObject<'local>,
) {
super::with_env(&mut env, |env| {
let bridge =
unsafe { &*(bridge_ptr as *const waterui_video_gpu::AndroidVideoSurfaceBridge) };
unsafe { bridge.attach_host(env, &host) }.unwrap_or_else(|error| {
panic!("WatcherJni.androidVideoSurfaceHostAttach failed: {error}")
});
});
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_androidVideoSurfaceHostDrop<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
bridge_ptr: jlong,
) {
unsafe {
drop(Box::from_raw(
bridge_ptr as *mut waterui_video_gpu::AndroidVideoSurfaceBridge,
));
}
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_androidVideoSurfaceHostSurfaceDestroyed<
'local,
>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
bridge_ptr: jlong,
) {
let bridge = unsafe { &*(bridge_ptr as *const waterui_video_gpu::AndroidVideoSurfaceBridge) };
bridge.surface_destroyed().unwrap_or_else(|error| {
panic!("WatcherJni.androidVideoSurfaceHostSurfaceDestroyed failed: {error}")
});
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_pictureBitmap<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
picture_ptr: jlong,
scale: jfloat,
) -> jlong {
use crate::IntoFFI;
use crate::components::picture::{WuiPictureHandle, bitmap_signal};
let picture = unsafe { &(*(picture_ptr as *const WuiPictureHandle)).0 };
bitmap_signal(picture, scale).into_ffi() as jlong
}
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_dropPicture<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
picture_ptr: jlong,
) {
use crate::components::picture::WuiPictureHandle;
drop(unsafe { Box::from_raw(picture_ptr as *mut WuiPictureHandle) });
}
#[cfg(all(target_os = "android", feature = "gpu"))]
struct JniGpuSurfaceState {
state: *mut crate::components::gpu_surface::WuiGpuSurfaceState,
window: Option<AndroidNativeWindow>,
}
#[cfg(all(target_os = "android", feature = "gpu"))]
struct AndroidGpuSurfaceRedrawTarget {
jvm: jni::JavaVM,
owner: Global<JObject<'static>>,
}
#[cfg(all(target_os = "android", feature = "gpu"))]
unsafe extern "C" fn wake_android_gpu_surface(context: *mut c_void) {
let target = unsafe { &*context.cast::<AndroidGpuSurfaceRedrawTarget>() };
super::with_attached_env(&target.jvm, |env| {
env.call_method(
&target.owner,
jni_str!("requestNativeRedraw"),
jni_sig!("()V"),
&[],
)
.expect("GpuSurface redraw callback failed to call requestNativeRedraw");
})
.expect("GpuSurface redraw callback failed to attach to JVM");
}
#[cfg(all(target_os = "android", feature = "gpu"))]
unsafe extern "C" fn drop_android_gpu_surface_redraw_target(context: *mut c_void) {
unsafe {
drop(Box::from_raw(
context.cast::<AndroidGpuSurfaceRedrawTarget>(),
));
}
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_gpuSurfaceCreate<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
owner: JObject<'local>,
renderer_ptr: jlong,
has_picture_in_picture_host_id: jboolean,
picture_in_picture_host_id: jlong,
wui_env_ptr: jlong,
) -> jlong {
super::with_env(&mut env, |env| {
let mut wui_surface = crate::components::gpu_surface::WuiGpuSurface {
surface: renderer_ptr as *mut c_void,
has_picture_in_picture_host_id,
picture_in_picture_host_id: picture_in_picture_host_id.cast_unsigned(),
};
let state = unsafe {
crate::components::gpu_surface::waterui_gpu_surface_create(
&raw mut wui_surface,
wui_env_ptr as *const crate::WuiEnv,
)
};
let redraw_target = Box::new(AndroidGpuSurfaceRedrawTarget {
jvm: env
.get_java_vm()
.expect("WatcherJni.gpuSurfaceCreate failed to access JavaVM"),
owner: env
.new_global_ref(owner)
.expect("WatcherJni.gpuSurfaceCreate failed to retain owner view"),
});
let redraw_context = Box::into_raw(redraw_target).cast::<c_void>();
unsafe {
crate::components::gpu_surface::waterui_gpu_surface_set_redraw_callback(
state,
redraw_context,
wake_android_gpu_surface,
drop_android_gpu_surface_redraw_target,
);
}
let wrapper = Box::new(JniGpuSurfaceState {
state,
window: None,
});
Box::into_raw(wrapper) as jlong
})
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_gpuSurfaceMeasure<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
width: jfloat,
height: jfloat,
) -> jobject {
super::with_env(&mut env, |env| {
let wrapper = unsafe { &*(state_ptr as *const JniGpuSurfaceState) };
let proposal = ProposalSize {
width: width.is_finite().then_some(width),
height: height.is_finite().then_some(height),
};
let dimensions =
unsafe { crate::components::gpu_surface::measure_state(&*wrapper.state, proposal) };
view_dimensions_to_java(env, &dimensions).into_raw()
})
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_gpuSurfacePriority<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
) -> jint {
let wrapper = unsafe { &*(state_ptr as *const JniGpuSurfaceState) };
unsafe { crate::components::gpu_surface::priority_state(&*wrapper.state) }
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_gpuSurfaceIsReady<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
) -> jboolean {
let wrapper = unsafe { &*(state_ptr as *const JniGpuSurfaceState) };
unsafe { crate::components::gpu_surface::waterui_gpu_surface_is_ready(wrapper.state) }
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_gpuSurfaceAttach<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
surface: JObject<'local>,
width: jint,
height: jint,
prefers_hdr: jboolean,
) {
super::with_env(&mut env, |env| {
let wrapper = unsafe { &mut *(state_ptr as *mut JniGpuSurfaceState) };
assert!(
wrapper.window.is_none(),
"WatcherJni.gpuSurfaceAttach called while a native surface is already attached"
);
let window = require_native_window(
unsafe { AndroidNativeWindow::from_surface(env.get_raw(), surface.as_raw()) },
"gpuSurfaceAttach",
);
unsafe {
crate::components::gpu_surface::waterui_gpu_surface_attach(
wrapper.state,
window.as_void_ptr(),
width.cast_unsigned(),
height.cast_unsigned(),
prefers_hdr,
);
}
wrapper.window = Some(window);
});
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_gpuSurfaceDetach<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
) {
let wrapper = unsafe { &mut *(state_ptr as *mut JniGpuSurfaceState) };
assert!(
wrapper.window.is_some(),
"WatcherJni.gpuSurfaceDetach called without an attached native surface"
);
unsafe {
crate::components::gpu_surface::waterui_gpu_surface_detach(wrapper.state);
}
drop(wrapper.window.take());
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_gpuSurfaceRender<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
width: jint,
height: jint,
scale: jfloat,
) -> jboolean {
let wrapper = unsafe { &mut *(state_ptr as *mut JniGpuSurfaceState) };
unsafe {
crate::components::gpu_surface::waterui_gpu_surface_render(
wrapper.state,
width.cast_unsigned(),
height.cast_unsigned(),
f64::from(scale),
)
}
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_gpuSurfaceSetInput<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
has_position: jboolean,
x: jfloat,
y: jfloat,
has_hit: jboolean,
hit_x: jfloat,
hit_y: jfloat,
gesture_active: jboolean,
pinch_scale: jfloat,
has_pinch_center: jboolean,
pinch_center_x: jfloat,
pinch_center_y: jfloat,
pan_offset_x: jfloat,
pan_offset_y: jfloat,
double_tap: jboolean,
) {
let wrapper = unsafe { &mut *(state_ptr as *mut JniGpuSurfaceState) };
let input = crate::components::gpu_surface::WuiGpuSurfaceInput {
pointer: crate::components::gpu_surface::WuiPointerState {
has_position,
x,
y,
has_hit,
hit_x,
hit_y,
},
gesture: crate::components::gpu_surface::WuiGestureState {
active: gesture_active,
pinch_scale,
has_pinch_center,
pinch_center_x,
pinch_center_y,
pan_offset_x,
pan_offset_y,
double_tap,
},
};
unsafe {
crate::components::gpu_surface::waterui_gpu_surface_set_input(wrapper.state, input);
}
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_gpuSurfaceSendInputEvent<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
kind: jint,
focused: jboolean,
modifiers: jint,
x: jdouble,
y: jdouble,
pressed: jboolean,
button: jint,
delta_x: jdouble,
delta_y: jdouble,
scroll_unit: jint,
finished: jboolean,
key: jni::objects::JString<'local>,
code: jni::objects::JString<'local>,
text: jni::objects::JString<'local>,
is_repeat: jboolean,
caret: jlong,
) -> jboolean {
use crate::components::gpu_surface_input::{WuiScrollUnit, WuiSurfaceInputEvent};
super::with_env(&mut env, |env| {
let state_ptr = state_ptr as *mut JniGpuSurfaceState;
let wrapper = unsafe { &mut *state_ptr };
let event = WuiSurfaceInputEvent {
kind: surface_input_event_kind(kind),
focused,
modifiers: u32::try_from(modifiers)
.expect("WatcherJni.gpuSurfaceSendInputEvent: modifier bits must be non-negative"),
x,
y,
pressed,
button: surface_pointer_button(button),
delta_x,
delta_y,
scroll_unit: match scroll_unit {
0 => WuiScrollUnit::Line,
1 => WuiScrollUnit::Pixel,
other => panic!("WatcherJni.gpuSurfaceSendInputEvent: unknown scroll unit {other}"),
},
finished,
key: java_surface_string(env, &key),
code: java_surface_string(env, &code),
text: java_surface_string(env, &text),
repeat: is_repeat,
caret,
};
unsafe {
crate::components::gpu_surface_input::waterui_gpu_surface_send_input_event(
wrapper.state,
event,
)
}
})
}
#[cfg(all(target_os = "android", feature = "gpu"))]
fn java_surface_string<'local>(
env: &mut Env<'local>,
value: &jni::objects::JString<'local>,
) -> crate::WuiStr {
use waterui_core::Str;
Str::from(super::convert::string_from_java(env, value)).into_ffi()
}
#[cfg(all(target_os = "android", feature = "gpu"))]
fn surface_input_event_kind(
kind: jint,
) -> crate::components::gpu_surface_input::WuiSurfaceInputEventKind {
use crate::components::gpu_surface_input::WuiSurfaceInputEventKind as Kind;
match kind {
0 => Kind::Focus,
1 => Kind::Modifiers,
2 => Kind::PointerMove,
3 => Kind::PointerButton,
4 => Kind::Scroll,
5 => Kind::Key,
6 => Kind::TextInput,
7 => Kind::CompositionStart,
8 => Kind::CompositionUpdate,
9 => Kind::CompositionCommit,
10 => Kind::CompositionCancel,
other => panic!("WatcherJni.gpuSurfaceSendInputEvent: unknown event kind {other}"),
}
}
#[cfg(all(target_os = "android", feature = "gpu"))]
fn surface_pointer_button(
button: jint,
) -> crate::components::gpu_surface_input::WuiSurfacePointerButton {
use crate::components::gpu_surface_input::WuiSurfacePointerButton as Button;
match button {
0 => Button::Primary,
1 => Button::Secondary,
2 => Button::Middle,
3 => Button::Back,
4 => Button::Forward,
other => panic!("WatcherJni.gpuSurfaceSendInputEvent: unknown pointer button {other}"),
}
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
const extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_gpuSurfaceWantsInputEvents<
'local,
>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
) -> jboolean {
let state_ptr = state_ptr as *const JniGpuSurfaceState;
let wrapper = unsafe { &*state_ptr };
unsafe {
crate::components::gpu_surface_input::waterui_gpu_surface_wants_input_events(wrapper.state)
}
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_gpuSurfaceImeCaret<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
) -> jni::sys::jfloatArray {
super::with_env(&mut env, |env| {
let state_ptr = state_ptr as *const JniGpuSurfaceState;
let wrapper = unsafe { &*state_ptr };
let mut rect = core::mem::MaybeUninit::<crate::components::layout::WuiRect>::uninit();
let has_caret = unsafe {
crate::components::gpu_surface_input::waterui_gpu_surface_ime_caret(
wrapper.state,
rect.as_mut_ptr(),
)
};
if !has_caret {
return core::ptr::null_mut();
}
let rect: waterui_layout::Rect = unsafe { crate::IntoRust::into_rust(rect.assume_init()) };
let origin = rect.origin();
let size = *rect.size();
let values: [jfloat; 4] = [origin.x, origin.y, size.width, size.height];
let array = env
.new_float_array(values.len())
.expect("gpuSurfaceImeCaret: failed to allocate the caret array");
array
.set_region(env, 0, &values)
.expect("gpuSurfaceImeCaret: failed to write the caret array");
array.into_raw()
})
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_gpuSurfaceAccessibilityLabel<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
) -> jni::sys::jstring {
super::with_env(&mut env, |env| {
let state_ptr = state_ptr as *const JniGpuSurfaceState;
let wrapper = unsafe { &*state_ptr };
let label = unsafe {
crate::components::gpu_surface::waterui_gpu_surface_accessibility_label(wrapper.state)
};
let text = unsafe { label.as_str() }.to_owned();
env.new_string(&text)
.expect("gpuSurfaceAccessibilityLabel: failed to create the Java string")
.into_raw()
})
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_gpuSurfaceDrop<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
) {
let wrapper = unsafe { Box::from_raw(state_ptr as *mut JniGpuSurfaceState) };
unsafe {
crate::components::gpu_surface::waterui_gpu_surface_drop(wrapper.state);
}
drop(wrapper);
}
#[cfg(all(target_os = "android", feature = "gpu"))]
struct JniAppliedFilterState {
state: *mut crate::components::applied_filter::WuiAppliedFilterState,
window: Option<AndroidNativeWindow>,
}
#[cfg(all(target_os = "android", feature = "gpu"))]
struct JniViewEffectState {
state: *mut crate::components::view_effect::WuiViewEffectState,
window: Option<AndroidNativeWindow>,
}
#[cfg(all(target_os = "android", feature = "gpu"))]
struct AndroidCaptureRedrawTarget {
jvm: jni::JavaVM,
owner: Global<JObject<'static>>,
}
#[cfg(all(target_os = "android", feature = "gpu"))]
unsafe extern "C" fn wake_android_capture(context: *mut c_void) {
let target = unsafe { &*context.cast::<AndroidCaptureRedrawTarget>() };
super::with_attached_env(&target.jvm, |env| {
env.call_method(
&target.owner,
jni_str!("requestNativeRedraw"),
jni_sig!("()V"),
&[],
)
.expect("capture redraw callback failed to call requestNativeRedraw");
})
.expect("capture redraw callback failed to attach to JVM");
}
#[cfg(all(target_os = "android", feature = "gpu"))]
unsafe extern "C" fn drop_android_capture_redraw_target(context: *mut c_void) {
unsafe {
drop(Box::from_raw(context.cast::<AndroidCaptureRedrawTarget>()));
}
}
#[cfg(all(target_os = "android", feature = "gpu"))]
fn android_capture_redraw_context<'local>(
env: &Env<'local>,
owner: JObject<'local>,
function: &str,
) -> *mut c_void {
let target = Box::new(AndroidCaptureRedrawTarget {
jvm: env
.get_java_vm()
.unwrap_or_else(|_| panic!("WatcherJni.{function} failed to access JavaVM")),
owner: env
.new_global_ref(owner)
.unwrap_or_else(|_| panic!("WatcherJni.{function} failed to retain owner view")),
});
Box::into_raw(target).cast::<c_void>()
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_appliedFilterCreate<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
owner: JObject<'local>,
filter_ptr: jlong,
wui_env_ptr: jlong,
) -> jlong {
super::with_env(&mut env, |env| {
let mut wui_filter = crate::components::applied_filter::WuiAppliedFilter {
content: core::ptr::null_mut(),
filter: filter_ptr as *mut c_void,
};
let state = unsafe {
crate::components::applied_filter::waterui_applied_filter_create(
&raw mut wui_filter,
wui_env_ptr as *const crate::WuiEnv,
)
};
let redraw_context = android_capture_redraw_context(env, owner, "appliedFilterCreate");
unsafe {
crate::components::applied_filter::waterui_applied_filter_set_redraw_callback(
state,
redraw_context,
wake_android_capture,
drop_android_capture_redraw_target,
);
}
Box::into_raw(Box::new(JniAppliedFilterState {
state,
window: None,
})) as jlong
})
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_appliedFilterAttach<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
surface: JObject<'local>,
input_width: jint,
input_height: jint,
prefers_hdr: jboolean,
) {
super::with_env(&mut env, |env| {
let wrapper = unsafe { &mut *(state_ptr as *mut JniAppliedFilterState) };
assert!(
wrapper.window.is_none(),
"WatcherJni.appliedFilterAttach called while a native surface is already attached"
);
let window = require_native_window(
unsafe { AndroidNativeWindow::from_surface(env.get_raw(), surface.as_raw()) },
"appliedFilterAttach",
);
unsafe {
crate::components::applied_filter::waterui_applied_filter_attach(
wrapper.state,
window.as_void_ptr(),
input_width.cast_unsigned(),
input_height.cast_unsigned(),
prefers_hdr,
);
}
wrapper.window = Some(window);
});
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_appliedFilterDetach<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
) {
let wrapper = unsafe { &mut *(state_ptr as *mut JniAppliedFilterState) };
assert!(
wrapper.window.is_some(),
"WatcherJni.appliedFilterDetach called without an attached native surface"
);
unsafe {
crate::components::applied_filter::waterui_applied_filter_detach(wrapper.state);
}
drop(wrapper.window.take());
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_appliedFilterSetup<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
) {
let wrapper = unsafe { &mut *(state_ptr as *mut JniAppliedFilterState) };
unsafe {
crate::components::applied_filter::waterui_applied_filter_setup(wrapper.state);
}
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_appliedFilterIsReady<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
) -> jboolean {
let wrapper = unsafe { &*(state_ptr as *const JniAppliedFilterState) };
unsafe { crate::components::applied_filter::waterui_applied_filter_is_ready(wrapper.state) }
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_appliedFilterResolveOutputSize<
'local,
>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
input_width: jint,
input_height: jint,
) -> jintArray {
super::with_env(&mut env, |env| {
let wrapper = unsafe { &mut *(state_ptr as *mut JniAppliedFilterState) };
let size = unsafe {
crate::components::applied_filter::waterui_applied_filter_resolve_output_size(
wrapper.state,
input_width.cast_unsigned(),
input_height.cast_unsigned(),
)
};
let values: [jint; 2] = [
jint::try_from(size.width)
.expect("appliedFilterResolveOutputSize: width exceeds jint capacity"),
jint::try_from(size.height)
.expect("appliedFilterResolveOutputSize: height exceeds jint capacity"),
];
let array = env
.new_int_array(values.len())
.expect("appliedFilterResolveOutputSize: failed to allocate the size array");
array
.set_region(env, 0, &values)
.expect("appliedFilterResolveOutputSize: failed to write the size array");
array.into_raw()
})
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_appliedFilterPrepareCapture<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
width: jint,
height: jint,
) {
let wrapper = unsafe { &mut *(state_ptr as *mut JniAppliedFilterState) };
unsafe {
crate::components::applied_filter::waterui_applied_filter_prepare_capture(
wrapper.state,
width.cast_unsigned(),
height.cast_unsigned(),
);
}
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_appliedFilterCaptureFormat<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
) -> jint {
let wrapper = unsafe { &*(state_ptr as *const JniAppliedFilterState) };
let format = unsafe {
crate::components::applied_filter::waterui_applied_filter_capture_format(wrapper.state)
};
format as jint
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_appliedFilterSetCaptureHardwareBuffer<
'local,
>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
hardware_buffer: JObject<'local>,
) -> jlong {
super::with_env(&mut env, |env| {
let wrapper = unsafe { &mut *(state_ptr as *mut JniAppliedFilterState) };
let buffer = unsafe {
crate::components::hardware_buffer::hardware_buffer_from_java(
env.get_raw().cast(),
hardware_buffer.as_raw().cast(),
)
};
let fence = unsafe {
crate::components::applied_filter::waterui_applied_filter_set_capture_hardware_buffer(
wrapper.state,
buffer.cast::<c_void>(),
)
};
fence as jlong
})
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_appliedFilterCompositeGpuSurface<
'local,
>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
surface_state_ptr: jlong,
x: jint,
y: jint,
width: jint,
height: jint,
scale: jfloat,
) {
let wrapper = unsafe { &mut *(state_ptr as *mut JniAppliedFilterState) };
let surface = unsafe { &mut *(surface_state_ptr as *mut JniGpuSurfaceState) };
unsafe {
crate::components::applied_filter::waterui_applied_filter_composite_gpu_surface(
wrapper.state,
surface.state,
x,
y,
width.cast_unsigned(),
height.cast_unsigned(),
f64::from(scale),
);
}
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_appliedFilterRender<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
width: jint,
height: jint,
) -> jboolean {
let wrapper = unsafe { &mut *(state_ptr as *mut JniAppliedFilterState) };
unsafe {
crate::components::applied_filter::waterui_applied_filter_render(
wrapper.state,
width.cast_unsigned(),
height.cast_unsigned(),
)
}
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_appliedFilterDrop<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
) {
let wrapper = unsafe { Box::from_raw(state_ptr as *mut JniAppliedFilterState) };
unsafe {
crate::components::applied_filter::waterui_applied_filter_drop(wrapper.state);
}
drop(wrapper);
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_viewEffectCreate<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
owner: JObject<'local>,
effect_ptr: jlong,
output_size_kind: jint,
output_width: jint,
output_height: jint,
output_scale: jfloat,
wui_env_ptr: jlong,
) -> jlong {
super::with_env(&mut env, |env| {
let mut wui_effect = crate::components::view_effect::WuiViewEffect {
content: core::ptr::null_mut(),
effect: effect_ptr as *mut c_void,
output_size: view_effect_output_size(
output_size_kind,
output_width,
output_height,
output_scale,
),
};
let state = unsafe {
crate::components::view_effect::waterui_view_effect_create(
&raw mut wui_effect,
wui_env_ptr as *const crate::WuiEnv,
)
};
let redraw_context = android_capture_redraw_context(env, owner, "viewEffectCreate");
unsafe {
crate::components::view_effect::waterui_view_effect_set_redraw_callback(
state,
redraw_context,
wake_android_capture,
drop_android_capture_redraw_target,
);
}
Box::into_raw(Box::new(JniViewEffectState {
state,
window: None,
})) as jlong
})
}
#[cfg(all(target_os = "android", feature = "gpu"))]
fn view_effect_output_size(
kind: jint,
width: jint,
height: jint,
scale: jfloat,
) -> crate::components::view_effect::WuiOutputSize {
use crate::components::view_effect::WuiOutputSize;
match kind {
0 => WuiOutputSize::MatchInput,
1 => WuiOutputSize::Fixed {
width: width.cast_unsigned(),
height: height.cast_unsigned(),
},
2 => WuiOutputSize::Scale { factor: scale },
other => panic!("WatcherJni.viewEffectCreate: unknown output size kind {other}"),
}
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_viewEffectAttach<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
surface: JObject<'local>,
input_width: jint,
input_height: jint,
prefers_hdr: jboolean,
) {
super::with_env(&mut env, |env| {
let wrapper = unsafe { &mut *(state_ptr as *mut JniViewEffectState) };
assert!(
wrapper.window.is_none(),
"WatcherJni.viewEffectAttach called while a native surface is already attached"
);
let window = require_native_window(
unsafe { AndroidNativeWindow::from_surface(env.get_raw(), surface.as_raw()) },
"viewEffectAttach",
);
unsafe {
crate::components::view_effect::waterui_view_effect_attach(
wrapper.state,
window.as_void_ptr(),
input_width.cast_unsigned(),
input_height.cast_unsigned(),
prefers_hdr,
);
}
wrapper.window = Some(window);
});
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_viewEffectDetach<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
) {
let wrapper = unsafe { &mut *(state_ptr as *mut JniViewEffectState) };
assert!(
wrapper.window.is_some(),
"WatcherJni.viewEffectDetach called without an attached native surface"
);
unsafe {
crate::components::view_effect::waterui_view_effect_detach(wrapper.state);
}
drop(wrapper.window.take());
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_viewEffectIsReady<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
) -> jboolean {
let wrapper = unsafe { &*(state_ptr as *const JniViewEffectState) };
unsafe { crate::components::view_effect::waterui_view_effect_is_ready(wrapper.state) }
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_viewEffectSetInputHardwareBuffer<
'local,
>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
hardware_buffer: JObject<'local>,
) -> jlong {
super::with_env(&mut env, |env| {
let wrapper = unsafe { &mut *(state_ptr as *mut JniViewEffectState) };
let buffer = unsafe {
crate::components::hardware_buffer::hardware_buffer_from_java(
env.get_raw().cast(),
hardware_buffer.as_raw().cast(),
)
};
let fence = unsafe {
crate::components::view_effect::waterui_view_effect_set_input_hardware_buffer(
wrapper.state,
buffer.cast::<c_void>(),
)
};
fence as jlong
})
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_viewEffectCompositeGpuSurface<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
surface_state_ptr: jlong,
x: jint,
y: jint,
width: jint,
height: jint,
scale: jfloat,
) {
let wrapper = unsafe { &mut *(state_ptr as *mut JniViewEffectState) };
let surface = unsafe { &mut *(surface_state_ptr as *mut JniGpuSurfaceState) };
unsafe {
crate::components::view_effect::waterui_view_effect_composite_gpu_surface(
wrapper.state,
surface.state,
x,
y,
width.cast_unsigned(),
height.cast_unsigned(),
f64::from(scale),
);
}
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_viewEffectRender<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
) -> jboolean {
let wrapper = unsafe { &mut *(state_ptr as *mut JniViewEffectState) };
unsafe { crate::components::view_effect::waterui_view_effect_render(wrapper.state) }
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_viewEffectDrop<'local>(
_env: EnvUnowned<'local>,
_class: JClass<'local>,
state_ptr: jlong,
) {
let wrapper = unsafe { Box::from_raw(state_ptr as *mut JniViewEffectState) };
unsafe {
crate::components::view_effect::waterui_view_effect_drop(wrapper.state);
}
drop(wrapper);
}
#[cfg(all(target_os = "android", feature = "gpu"))]
struct AndroidCaptureCompletion {
jvm: jni::JavaVM,
runnable: Global<JObject<'static>>,
}
#[cfg(all(target_os = "android", feature = "gpu"))]
unsafe extern "C" fn run_android_capture_completion(context: *mut c_void) {
let completion = unsafe { &*context.cast::<AndroidCaptureCompletion>() };
super::with_attached_env(&completion.jvm, |env| {
env.call_method(&completion.runnable, jni_str!("run"), jni_sig!("()V"), &[])
.expect("capture completion failed to call Runnable.run");
})
.expect("capture completion failed to attach to JVM");
}
#[cfg(all(target_os = "android", feature = "gpu"))]
unsafe extern "C" fn drop_android_capture_completion(context: *mut c_void) {
unsafe {
drop(Box::from_raw(context.cast::<AndroidCaptureCompletion>()));
}
}
#[cfg(all(target_os = "android", feature = "gpu"))]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_gpuCaptureFenceOnComplete<'local>(
mut env: EnvUnowned<'local>,
_class: JClass<'local>,
fence_ptr: jlong,
completion: JObject<'local>,
) {
super::with_env(&mut env, |env| {
let completion = Box::new(AndroidCaptureCompletion {
jvm: env
.get_java_vm()
.expect("WatcherJni.gpuCaptureFenceOnComplete failed to access JavaVM"),
runnable: env
.new_global_ref(completion)
.expect("WatcherJni.gpuCaptureFenceOnComplete failed to retain the completion"),
});
let context = Box::into_raw(completion).cast::<c_void>();
unsafe {
crate::components::gpu_surface::waterui_gpu_capture_fence_on_complete(
fence_ptr as *mut crate::components::gpu_surface::WuiGpuCaptureFence,
context,
run_android_capture_completion,
drop_android_capture_completion,
);
}
});
}