Skip to content
13 changes: 11 additions & 2 deletions crates/perry-ui-android/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ pub fn add_keyboard_shortcut(_key_ptr: *const u8, _modifiers: f64, _callback: f6

extern "C" {
fn js_closure_call1(closure: *const u8, arg: f64) -> f64;
fn js_nanbox_get_pointer(value: f64) -> i64;
}

thread_local! {
Expand Down Expand Up @@ -406,7 +407,11 @@ pub fn on_terminate(callback: f64) {
pub fn handle_activate() {
ON_ACTIVATE_CALLBACK.with(|c| {
if let Some(callback) = *c.borrow() {
let ptr = callback.to_bits() as *const u8;
// Unbox NaN-boxed closure (same as callback.rs / iOS/macOS).
let ptr = unsafe { js_nanbox_get_pointer(callback) } as *const u8;
if ptr.is_null() {
return;
}
unsafe {
js_closure_call1(ptr, 0.0);
}
Expand All @@ -418,7 +423,11 @@ pub fn handle_activate() {
pub fn handle_terminate() {
ON_TERMINATE_CALLBACK.with(|c| {
if let Some(callback) = *c.borrow() {
let ptr = callback.to_bits() as *const u8;
// Unbox NaN-boxed closure — see invoke0 / callback.rs for details.
let ptr = unsafe { js_nanbox_get_pointer(callback) } as *const u8;
if ptr.is_null() {
return;
}
unsafe {
js_closure_call1(ptr, 0.0);
}
Expand Down
17 changes: 12 additions & 5 deletions crates/perry-ui-android/src/callback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,10 @@ pub fn invoke0(key: i64) {
guard.as_ref().and_then(|m| m.get(&key).copied())
};
if let Some(closure_f64) = closure_f64 {
let closure_ptr = closure_f64.to_bits() as *const u8;
// Must unbox the NaN-boxed closure — same as iOS/macOS.
// Using to_bits() as a raw pointer leaves the 0x7ffd tag in the
// high bits and crashes (often later on RenderThread via heap corruption).
let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) } as *const u8;
unsafe {
__android_log_print(
3,
Expand Down Expand Up @@ -133,7 +136,8 @@ pub fn invoke1(key: i64, arg: f64) {
guard.as_ref().and_then(|m| m.get(&key).copied())
};
if let Some(closure_f64) = closure_f64 {
let closure_ptr = closure_f64.to_bits() as *const u8;
// Unbox NaN-boxed closure — see invoke0 for details.
let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) } as *const u8;
unsafe {
js_closure_call1(closure_ptr, arg);
}
Expand All @@ -147,7 +151,8 @@ pub fn invoke2(key: i64, arg1: f64, arg2: f64) {
guard.as_ref().and_then(|m| m.get(&key).copied())
};
if let Some(closure_f64) = closure_f64 {
let closure_ptr = closure_f64.to_bits() as *const u8;
// Unbox NaN-boxed closure — see invoke0 for details.
let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) } as *const u8;
unsafe {
js_closure_call2(closure_ptr, arg1, arg2);
}
Expand All @@ -162,7 +167,8 @@ pub fn invoke4(key: i64, arg0: f64, arg1: f64, arg2: f64, arg3: f64) {
guard.as_ref().and_then(|m| m.get(&key).copied())
};
if let Some(closure_f64) = closure_f64 {
let closure_ptr = closure_f64.to_bits() as *const u8;
// Unbox NaN-boxed closure — see invoke0 for details.
let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) } as *const u8;
unsafe {
js_closure_call4(closure_ptr, arg0, arg1, arg2, arg3);
}
Expand All @@ -179,7 +185,8 @@ pub fn invoke_with_string_array(key: i64, paths: &[String]) {
guard.as_ref().and_then(|m| m.get(&key).copied())
};
if let Some(closure_f64) = closure_f64 {
let closure_ptr = closure_f64.to_bits() as *const u8;
// Unbox NaN-boxed closure — see invoke0 for details.
let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) } as *const u8;
unsafe {
let mut arr = js_array_alloc(paths.len() as u32);
for p in paths {
Expand Down
5 changes: 3 additions & 2 deletions crates/perry-ui-android/src/drag_drop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ extern "C" {
fn js_nanbox_string(ptr: i64) -> f64;
fn js_closure_call0(closure: *const u8) -> f64;
fn js_closure_call1(closure: *const u8, arg: f64) -> f64;
fn js_nanbox_get_pointer(value: f64) -> i64;
// Converts an arbitrary JS value to a runtime `StringHeader` (typed here as
// `*const u8` so it feeds `str_from_header`, the same way `clipboard.rs`
// treats runtime string pointers). Defined in
Expand Down Expand Up @@ -242,7 +243,7 @@ pub extern "C" fn Java_com_perry_app_PerryBridge_nativeInvokeDropCallback(
let Some(closure_f64) = callback::get(key as i64) else {
return;
};
let closure_ptr = closure_f64.to_bits() as *const u8;
let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) } as *const u8;
if closure_ptr.is_null() {
return;
}
Expand Down Expand Up @@ -302,7 +303,7 @@ pub extern "C" fn Java_com_perry_app_PerryBridge_nativeInvokeDragProvider<'local
/// or returned `undefined`/`null`.
fn drag_provider_payload(key: i64) -> Option<String> {
let closure_f64 = callback::get(key)?;
let closure_ptr = closure_f64.to_bits() as *const u8;
let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) } as *const u8;
if closure_ptr.is_null() {
return None;
}
Expand Down
33 changes: 33 additions & 0 deletions crates/perry-ui-android/src/ffi/basic_widgets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,39 @@ pub extern "C" fn perry_ui_text_create(text_ptr: i64) -> i64 {
})
}

/// `Text(content, id)` — create + register for `setText(id, value)`.
/// Other platforms export this; Android was missing it, so dlopen of apps
/// that use reactive Text ids failed with:
/// `cannot locate symbol "perry_ui_text_create_with_id"`.
#[no_mangle]
pub extern "C" fn perry_ui_text_create_with_id(text_ptr: i64, id_ptr: i64) -> i64 {
catch_panic("perry_ui_text_create_with_id", || {
let handle = widgets::text::create(text_ptr as *const u8);
if id_ptr != 0 {
let id = app::str_from_header(id_ptr as *const u8);
widgets::text_registry::register_text_id_handler(handle, id.as_ptr(), id.len());
}
handle
})
}

/// Direct `setText(id, value)` entry for the `import { setText }` surface.
#[no_mangle]
pub extern "C" fn perry_ui_set_text(id_ptr: i64, value_ptr: i64) {
if id_ptr == 0 {
return;
}
catch_panic_void("perry_ui_set_text", || {
let id = app::str_from_header(id_ptr as *const u8);
let val = if value_ptr == 0 {
""
} else {
app::str_from_header(value_ptr as *const u8)
};
widgets::text_registry::set_text_handler(id.as_ptr(), id.len(), val.as_ptr(), val.len());
});
}

#[no_mangle]
pub extern "C" fn perry_ui_button_create(label_ptr: i64, on_press: f64) -> i64 {
catch_panic("perry_ui_button_create", || {
Expand Down
40 changes: 24 additions & 16 deletions crates/perry-ui-android/src/ffi/image_nav.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,16 +98,17 @@ pub extern "C" fn perry_ui_widget_set_control_size(handle: i64, size: i64) {
}

#[no_mangle]
pub extern "C" fn perry_ui_widget_set_corner_radius(handle: i64, radius: f64) {
widgets::set_corner_radius(handle, radius);
pub extern "C" fn perry_ui_widget_set_corner_radius(handle: f64, radius: f64) {
let h = widgets::decode_js_handle_f64(handle);
widgets::set_corner_radius(h, radius);
}

/// Set drop shadow via Material `setElevation` + (API 28+)
/// `setOutlineSpotShadowColor` / `setOutlineAmbientShadowColor`. See
/// `widgets::set_shadow` for the full mapping rationale (issue #185 Phase B).
#[no_mangle]
pub extern "C" fn perry_ui_widget_set_shadow(
handle: i64,
handle: f64,
r: f64,
g: f64,
b: f64,
Expand All @@ -116,23 +117,25 @@ pub extern "C" fn perry_ui_widget_set_shadow(
offset_x: f64,
offset_y: f64,
) {
widgets::set_shadow(handle, r, g, b, a, blur, offset_x, offset_y);
let h = widgets::decode_js_handle_f64(handle);
widgets::set_shadow(h, r, g, b, a, blur, offset_x, offset_y);
}

#[no_mangle]
pub extern "C" fn perry_ui_widget_set_background_color(
handle: i64,
handle: f64,
r: f64,
g: f64,
b: f64,
a: f64,
) {
widgets::set_background_color(handle, r, g, b, a);
let h = widgets::decode_js_handle_f64(handle);
widgets::set_background_color(h, r, g, b, a);
}

#[no_mangle]
pub extern "C" fn perry_ui_widget_set_background_gradient(
handle: i64,
handle: f64,
r1: f64,
g1: f64,
b1: f64,
Expand All @@ -143,40 +146,45 @@ pub extern "C" fn perry_ui_widget_set_background_gradient(
a2: f64,
direction: f64,
) {
widgets::set_background_gradient(handle, r1, g1, b1, a1, r2, g2, b2, a2, direction);
let h = widgets::decode_js_handle_f64(handle);
widgets::set_background_gradient(h, r1, g1, b1, a1, r2, g2, b2, a2, direction);
}

#[no_mangle]
pub extern "C" fn perry_ui_widget_set_border_color(handle: i64, r: f64, g: f64, b: f64, a: f64) {
pub extern "C" fn perry_ui_widget_set_border_color(handle: f64, r: f64, g: f64, b: f64, a: f64) {
catch_panic_void("perry_ui_widget_set_border_color", || {
widgets::set_border_color(handle, r, g, b, a)
let h = widgets::decode_js_handle_f64(handle);
widgets::set_border_color(h, r, g, b, a)
});
}

#[no_mangle]
pub extern "C" fn perry_ui_widget_set_border_width(handle: i64, width: f64) {
pub extern "C" fn perry_ui_widget_set_border_width(handle: f64, width: f64) {
catch_panic_void("perry_ui_widget_set_border_width", || {
widgets::set_border_width(handle, width)
let h = widgets::decode_js_handle_f64(handle);
widgets::set_border_width(h, width)
});
}

#[no_mangle]
pub extern "C" fn perry_ui_widget_set_edge_insets(
handle: i64,
handle: f64,
top: f64,
left: f64,
bottom: f64,
right: f64,
) {
catch_panic_void("perry_ui_widget_set_edge_insets", || {
widgets::set_edge_insets(handle, top, left, bottom, right)
let h = widgets::decode_js_handle_f64(handle);
widgets::set_edge_insets(h, top, left, bottom, right)
});
}

#[no_mangle]
pub extern "C" fn perry_ui_widget_set_opacity(handle: i64, alpha: f64) {
pub extern "C" fn perry_ui_widget_set_opacity(handle: f64, alpha: f64) {
catch_panic_void("perry_ui_widget_set_opacity", || {
widgets::set_opacity(handle, alpha)
let h = widgets::decode_js_handle_f64(handle);
widgets::set_opacity(h, alpha)
});
}

Expand Down
15 changes: 9 additions & 6 deletions crates/perry-ui-android/src/ffi/tabbar_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ pub extern "C" fn perry_ui_tabbar_set_selected(tabbar_handle: i64, index: i64) {
// =============================================================================

#[no_mangle]
pub extern "C" fn perry_ui_button_set_text_color(handle: i64, r: f64, g: f64, b: f64, a: f64) {
pub extern "C" fn perry_ui_button_set_text_color(handle: f64, r: f64, g: f64, b: f64, a: f64) {
catch_panic_void("perry_ui_button_set_text_color", || {
widgets::button::set_text_color(handle, r, g, b, a)
let h = widgets::decode_js_handle_f64(handle);
widgets::button::set_text_color(h, r, g, b, a)
});
}

Expand Down Expand Up @@ -99,16 +100,18 @@ pub extern "C" fn perry_ui_widget_set_hugging(handle: i64, priority: f64) {
// =============================================================================

#[no_mangle]
pub extern "C" fn perry_ui_widget_set_width(handle: i64, width: f64) {
pub extern "C" fn perry_ui_widget_set_width(handle: f64, width: f64) {
catch_panic_void("perry_ui_widget_set_width", || {
widgets::set_width(handle, width)
let h = widgets::decode_js_handle_f64(handle);
widgets::set_width(h, width)
});
}

#[no_mangle]
pub extern "C" fn perry_ui_widget_set_height(handle: i64, height: f64) {
pub extern "C" fn perry_ui_widget_set_height(handle: f64, height: f64) {
catch_panic_void("perry_ui_widget_set_height", || {
widgets::set_height(handle, height)
let h = widgets::decode_js_handle_f64(handle);
widgets::set_height(h, height)
});
}

Expand Down
20 changes: 12 additions & 8 deletions crates/perry-ui-android/src/ffi/text_scroll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,18 +141,21 @@ pub extern "C" fn perry_system_get_app_icon(_path: i64) -> i64 {
// =============================================================================

#[no_mangle]
pub extern "C" fn perry_ui_text_set_color(handle: i64, r: f64, g: f64, b: f64, a: f64) {
widgets::text::set_color(handle, r, g, b, a);
pub extern "C" fn perry_ui_text_set_color(handle: f64, r: f64, g: f64, b: f64, a: f64) {
let h = widgets::decode_js_handle_f64(handle);
widgets::text::set_color(h, r, g, b, a);
}

#[no_mangle]
pub extern "C" fn perry_ui_text_set_font_size(handle: i64, size: f64) {
widgets::text::set_font_size(handle, size);
pub extern "C" fn perry_ui_text_set_font_size(handle: f64, size: f64) {
let h = widgets::decode_js_handle_f64(handle);
widgets::text::set_font_size(h, size);
}

#[no_mangle]
pub extern "C" fn perry_ui_text_set_font_weight(handle: i64, size: f64, weight: f64) {
widgets::text::set_font_weight(handle, size, weight);
pub extern "C" fn perry_ui_text_set_font_weight(handle: f64, size: f64, weight: f64) {
let h = widgets::decode_js_handle_f64(handle);
widgets::text::set_font_weight(h, size, weight);
}

#[no_mangle]
Expand Down Expand Up @@ -191,8 +194,9 @@ pub extern "C" fn perry_ui_text_set_text_alignment(handle: i64, alignment: i64)
}

#[no_mangle]
pub extern "C" fn perry_ui_button_set_bordered(handle: i64, bordered: f64) {
widgets::button::set_bordered(handle, bordered != 0.0);
pub extern "C" fn perry_ui_button_set_bordered(handle: f64, bordered: f64) {
let h = widgets::decode_js_handle_f64(handle);
widgets::button::set_bordered(h, bordered != 0.0);
}

#[no_mangle]
Expand Down
3 changes: 2 additions & 1 deletion crates/perry-ui-android/src/pointer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use jni::objects::JValue;

extern "C" {
fn js_closure_call1(closure: *const u8, arg: f64) -> f64;
fn js_nanbox_get_pointer(value: f64) -> i64;
fn js_pointer_event_new(x: f64, y: f64, button: u32, pointer_type: u32) -> f64;
}

Expand All @@ -42,7 +43,7 @@ pub extern "C" fn Java_com_perry_app_PerryBridge_nativeInvokePointerCallback(
let Some(closure_f64) = closure_f64 else {
return;
};
let closure_ptr = closure_f64.to_bits() as *const u8;
let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) } as *const u8;
if closure_ptr.is_null() {
return;
}
Expand Down
4 changes: 3 additions & 1 deletion crates/perry-ui-android/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,9 @@ pub fn state_set(handle: i64, value: f64) {
.unwrap_or_default()
});
for callback_f64 in callbacks_snapshot {
let closure_ptr = callback_f64.to_bits() as *const u8;
// onChange callbacks are NaN-boxed closures (same as button path).
// Do not use to_bits() as a raw pointer — leaves the 0x7ffd tag.
let closure_ptr = unsafe { js_nanbox_get_pointer(callback_f64) } as *const u8;
unsafe {
js_closure_call1(closure_ptr, value);
}
Expand Down
Loading
Loading