use alloc::boxed::Box;
use core::fmt;
use waterui::component::list::Move;
use waterui_core::Environment;
use waterui_core::handler::BoxedAction;
use crate::bridge::closure::RetainedCallback;
use crate::{IntoFFI, WuiEnv};
opaque!(WuiAction, RetainedCallback<BoxedAction<()>>, action);
impl IntoFFI for BoxedAction<()> {
type FFI = *mut WuiAction;
fn into_ffi(self) -> Self::FFI {
RetainedCallback::new(self).into_ffi()
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_call_action(action: *const WuiAction, env: *const WuiEnv) {
let action = unsafe { crate::borrow_ffi(action) }.0.clone();
let env = unsafe { crate::borrow_ffi(env) }.0.clone();
action.call(|action| action(&env));
}
type IndexCallback = Box<dyn Fn(&Environment, usize)>;
pub struct IndexHandler(pub IndexCallback);
impl fmt::Debug for IndexHandler {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("IndexHandler").finish_non_exhaustive()
}
}
opaque!(WuiIndexAction, RetainedCallback<IndexHandler>, index_action);
impl crate::IntoNullableFFI for waterui::component::list::OnDelete {
type FFI = *mut WuiIndexAction;
fn into_ffi(self) -> Self::FFI {
RetainedCallback::new(IndexHandler(self)).into_ffi()
}
fn null() -> Self::FFI {
core::ptr::null_mut()
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_call_index_action(
action: *const WuiIndexAction,
env: *const WuiEnv,
index: usize,
) {
let action = unsafe { crate::borrow_ffi(action) }.0.clone();
let env = unsafe { crate::borrow_ffi(env) }.0.clone();
action.call(|action| (action.0)(&env, index));
}
type MoveCallback = Box<dyn Fn(&Environment, Move)>;
pub struct MoveHandler(pub MoveCallback);
impl fmt::Debug for MoveHandler {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MoveHandler").finish_non_exhaustive()
}
}
opaque!(WuiMoveAction, RetainedCallback<MoveHandler>, move_action);
impl crate::IntoNullableFFI for waterui::component::list::OnMove {
type FFI = *mut WuiMoveAction;
fn into_ffi(self) -> Self::FFI {
RetainedCallback::new(MoveHandler(self)).into_ffi()
}
fn null() -> Self::FFI {
core::ptr::null_mut()
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_call_move_action(
action: *const WuiMoveAction,
env: *const WuiEnv,
from_index: usize,
to_index: usize,
) {
let action = unsafe { crate::borrow_ffi(action) }.0.clone();
let env = unsafe { crate::borrow_ffi(env) }.0.clone();
action.call(|action| (action.0)(&env, Move::new(from_index, to_index)));
}
#[cfg(all(test, feature = "c-api"))]
mod tests {
use alloc::rc::Rc;
use alloc::sync::Arc;
use core::cell::Cell;
use core::sync::atomic::{AtomicUsize, Ordering};
use super::*;
use crate::WuiEnv;
fn test_env() -> *mut WuiEnv {
waterui::Environment::new().into_ffi()
}
#[test]
fn call_action_executes_handler() {
let hits = Arc::new(AtomicUsize::new(0));
let hits_for_action = Arc::clone(&hits);
let action: BoxedAction<()> = Box::new(move |_| {
hits_for_action.fetch_add(1, Ordering::SeqCst);
});
let action_ptr = action.into_ffi();
let env_ptr = test_env();
unsafe {
waterui_call_action(action_ptr, env_ptr);
waterui_call_action(action_ptr, env_ptr);
}
assert_eq!(hits.load(Ordering::SeqCst), 2);
unsafe {
waterui_drop_action(action_ptr);
let _ = Box::from_raw(env_ptr.cast::<waterui::Environment>());
}
}
#[test]
fn action_survives_reentrant_native_drop_until_callback_returns() {
let action_ptr = Rc::new(Cell::new(core::ptr::null_mut::<WuiAction>()));
let callback_finished = Rc::new(Cell::new(false));
let action_ptr_for_callback = Rc::clone(&action_ptr);
let callback_finished_for_callback = Rc::clone(&callback_finished);
let action: BoxedAction<()> = Box::new(move |_| {
unsafe { waterui_drop_action(action_ptr_for_callback.get()) };
callback_finished_for_callback.set(true);
});
let action = action.into_ffi();
action_ptr.set(action);
let env = test_env();
unsafe { waterui_call_action(action, env) };
assert!(callback_finished.get());
unsafe {
let _ = Box::from_raw(env.cast::<waterui::Environment>());
}
}
}