waterui_ffi/lib.rs
1//! # WaterUI FFI
2//!
3//! This crate provides a set of traits and utilities for safely converting between
4//! Rust types and FFI-compatible representations. It is designed to work in `no_std`
5//! environments and provides a clean, type-safe interface for FFI operations.
6//!
7//! The core functionality includes:
8//! - `IntoFFI` trait for converting Rust types to FFI-compatible representations
9//! - `IntoRust` trait for safely converting FFI types back to Rust types
10//! - Support for opaque type handling across FFI boundaries
11//! - Array and closure utilities for FFI interactions
12//!
13//! This library aims to minimize the unsafe code needed when working with FFI while
14//! maintaining performance and flexibility.
15
16#![no_std]
17extern crate alloc;
18#[cfg(feature = "std")]
19extern crate std;
20#[macro_use]
21mod macros;
22pub mod action;
23pub mod animation;
24pub mod array;
25pub mod closure;
26pub mod color;
27pub mod components;
28pub mod event;
29pub mod gesture;
30mod type_id;
31use tracing_subscriber::layer::SubscriberExt;
32use tracing_subscriber::util::SubscriberInitExt;
33pub use type_id::WuiTypeId;
34pub mod id;
35pub mod reactive;
36pub mod theme;
37mod ty;
38pub mod views;
39use core::ptr::null_mut;
40
41use alloc::boxed::Box;
42use executor_core::{init_global_executor, init_local_executor};
43use waterui::{AnyView, Str, View};
44use waterui_core::Metadata;
45
46pub mod app;
47pub mod window;
48use waterui_core::metadata::MetadataKey;
49
50use crate::array::WuiArray;
51#[macro_export]
52macro_rules! export {
53 () => {
54 const _: () = {
55 /// Initializes the WaterUI runtime and creates a default environment.
56 ///
57 /// Native should:
58 /// 1. Call this once at startup
59 /// 2. Install theme settings into the returned environment
60 /// 3. Pass the environment to `waterui_app()`
61 ///
62 /// # Safety
63 /// This function must be called on main thread, once only.
64 #[unsafe(no_mangle)]
65 pub unsafe extern "C" fn waterui_init() -> *mut $crate::WuiEnv {
66 unsafe {
67 $crate::__init();
68 }
69 let env = waterui::Environment::new();
70 $crate::IntoFFI::into_ffi(env)
71 }
72
73 ::waterui::hot_reloadable_library!(app);
74
75 /// Creates the application from the user's `app(env)` function.
76 ///
77 /// Takes ownership of the environment (with theme already installed) from native,
78 /// calls the user's `app(env: Environment) -> App` function, and returns the App.
79 ///
80 /// The environment is returned inside the App struct for native to use during rendering.
81 ///
82 /// # Safety
83 /// - `env` must be a valid pointer from `waterui_init()` or native env creation
84 /// - This function takes ownership of the environment
85 /// - This function must be called on main thread
86 #[unsafe(no_mangle)]
87 #[allow(unexpected_cfgs)]
88 pub unsafe extern "C" fn waterui_app(env: *mut $crate::WuiEnv) -> $crate::app::WuiApp {
89 // Take ownership of the environment
90 let env: waterui::Environment = unsafe { $crate::IntoRust::into_rust(env) };
91
92 // Call user's app(env: Environment) -> App
93 let app: waterui::app::App = app(env);
94
95 $crate::IntoFFI::into_ffi(app)
96 }
97 };
98 };
99}
100
101/// # Safety
102/// You have to ensure this is only called once, and on main thread.
103#[doc(hidden)]
104#[inline(always)]
105pub unsafe fn __init() {
106 #[cfg(target_os = "android")]
107 unsafe {
108 native_executor::android::register_android_main_thread()
109 .expect("Failed to register Android main thread");
110 }
111 // Forwards panics to tracing
112 std::panic::set_hook(Box::new(|info| {
113 tracing_panic::panic_hook(info);
114 }));
115
116 // Forwards tracing to platform's logging system
117 #[cfg(target_os = "android")]
118 {
119 tracing_subscriber::registry()
120 .with(tracing_android::layer("WaterUI").expect("Failed to create Android log layer"))
121 .init();
122 }
123
124 #[cfg(target_vendor = "apple")]
125 {
126 tracing_subscriber::registry()
127 .with(tracing_oslog::OsLogger::new("dev.waterui", "default"))
128 .init();
129 }
130
131 #[cfg(not(any(target_os = "android", target_vendor = "apple")))]
132 {
133 tracing_subscriber::fmt()
134 .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
135 .init();
136 }
137
138 init_global_executor(native_executor::NativeExecutor::new());
139 init_local_executor(native_executor::NativeExecutor::new());
140}
141
142/// Defines a trait for converting Rust types to FFI-compatible representations.
143///
144/// This trait is used to convert Rust types that are not directly FFI-compatible
145/// into types that can be safely passed across the FFI boundary. Implementors
146/// must specify an FFI-compatible type and provide conversion logic.
147///
148/// # Examples
149///
150/// ```ignore
151/// impl IntoFFI for MyStruct {
152/// type FFI = *mut MyStruct;
153/// fn into_ffi(self) -> Self::FFI {
154/// Box::into_raw(Box::new(self))
155/// }
156/// }
157/// ```
158pub trait IntoFFI: 'static {
159 /// The FFI-compatible type that this Rust type converts to.
160 type FFI: 'static;
161
162 /// Converts this Rust type into its FFI-compatible representation.
163 fn into_ffi(self) -> Self::FFI;
164}
165
166pub trait IntoNullableFFI: 'static {
167 type FFI: 'static;
168 fn into_ffi(self) -> Self::FFI;
169 fn null() -> Self::FFI;
170}
171
172impl<T: IntoNullableFFI> IntoFFI for Option<T> {
173 type FFI = T::FFI;
174
175 fn into_ffi(self) -> Self::FFI {
176 match self {
177 Some(value) => value.into_ffi(),
178 None => T::null(),
179 }
180 }
181}
182
183impl<T: IntoNullableFFI> IntoFFI for T {
184 type FFI = T::FFI;
185
186 fn into_ffi(self) -> Self::FFI {
187 <T as IntoNullableFFI>::into_ffi(self)
188 }
189}
190
191pub trait InvalidValue {
192 fn invalid() -> Self;
193}
194
195// Hot reload configuration FFI functions are in hot_reload.rs
196
197/// Defines a marker trait for types that should be treated as opaque when crossing FFI boundaries.
198///
199/// Opaque types are typically used when the internal structure of a type is not relevant
200/// to foreign code and only the Rust side needs to understand the full implementation details.
201/// This trait automatically provides implementations of `IntoFFI` and `IntoRust` for
202/// any type that implements it, handling conversion to and from raw pointers.
203///
204/// # Examples
205///
206/// ```ignore
207/// struct MyInternalStruct {
208/// data: Vec<u32>,
209/// state: String,
210/// }
211///
212/// // By marking this as OpaqueType, foreign code only needs to deal with opaque pointers
213/// impl OpaqueType for MyInternalStruct {}
214/// ```
215pub trait OpaqueType: 'static {}
216
217impl<T: OpaqueType> IntoNullableFFI for T {
218 type FFI = *mut T;
219 fn into_ffi(self) -> Self::FFI {
220 Box::into_raw(Box::new(self))
221 }
222 fn null() -> Self::FFI {
223 null_mut()
224 }
225}
226
227impl<T: OpaqueType> IntoRust for *mut T {
228 type Rust = Option<T>;
229 unsafe fn into_rust(self) -> Self::Rust {
230 if self.is_null() {
231 None
232 } else {
233 unsafe { Some(*Box::from_raw(self)) }
234 }
235 }
236}
237/// Defines a trait for converting FFI-compatible types back to native Rust types.
238///
239/// This trait is complementary to `IntoFFI` and is used to convert FFI-compatible
240/// representations back into their original Rust types. This is typically used
241/// when receiving data from FFI calls that need to be processed in Rust code.
242///
243/// # Safety
244///
245/// Implementations of this trait are inherently unsafe as they involve converting
246/// raw pointers or other FFI-compatible types into Rust types, which requires
247/// ensuring memory safety, proper ownership, and correct type interpretation.
248///
249/// # Examples
250///
251/// ```ignore
252/// impl IntoRust for *mut MyStruct {
253/// type Rust = MyStruct;
254///
255/// unsafe fn into_rust(self) -> Self::Rust {
256/// if self.is_null() {
257/// panic!("Null pointer provided");
258/// }
259/// *Box::from_raw(self)
260/// }
261/// }
262/// ```
263pub trait IntoRust {
264 /// The native Rust type that this FFI-compatible type converts to.
265 type Rust;
266
267 /// Converts this FFI-compatible type into its Rust equivalent.
268 ///
269 /// # Safety
270 /// The caller must ensure that the FFI value being converted is valid and
271 /// properly initialized. Improper use may lead to undefined behavior.
272 unsafe fn into_rust(self) -> Self::Rust;
273}
274
275ffi_safe!(u8, u16, u32, u64, i8, i16, i32, i64, f32, f64, bool);
276
277opaque!(WuiEnv, waterui::Environment, env);
278
279opaque!(WuiAnyView, waterui::AnyView, anyview);
280
281/// Creates a new environment instance
282#[unsafe(no_mangle)]
283pub extern "C" fn waterui_env_new() -> *mut WuiEnv {
284 let env = waterui::Environment::new();
285 env.into_ffi()
286}
287
288/// Gets the id of the anyview type as a 128-bit value for O(1) comparison.
289#[unsafe(no_mangle)]
290pub extern "C" fn waterui_anyview_id() -> WuiTypeId {
291 WuiTypeId::of::<AnyView>()
292}
293
294/// Clones an existing environment instance
295///
296/// # Safety
297/// The caller must ensure that `env` is a valid pointer to a properly initialized
298/// `waterui::Environment` instance and that the environment remains valid for the
299/// duration of this function call.
300#[unsafe(no_mangle)]
301pub unsafe extern "C" fn waterui_clone_env(env: *const WuiEnv) -> *mut WuiEnv {
302 unsafe { (*env).clone().into_ffi() }
303}
304
305/// Gets the body of a view given the environment
306///
307/// # Safety
308/// The caller must ensure that both `view` and `env` are valid pointers to properly
309/// initialized instances and that they remain valid for the duration of this function call.
310/// The `view` pointer will be consumed and should not be used after this call.
311#[unsafe(no_mangle)]
312pub unsafe extern "C" fn waterui_view_body(
313 view: *mut WuiAnyView,
314 env: *mut WuiEnv,
315) -> *mut WuiAnyView {
316 unsafe {
317 let view = view.into_rust();
318 let body = view.body(&*env);
319
320 let body = AnyView::new(body);
321
322 body.into_ffi()
323 }
324}
325
326/// Gets the id of a view as a 128-bit value for O(1) comparison.
327///
328/// - Normal build: Returns the view's `TypeId` (guaranteed unique)
329/// - Hot reload: Returns 128-bit hash of `type_name()` (stable across dylibs)
330///
331/// # Safety
332/// The caller must ensure that `view` is a valid pointer to a properly
333/// initialized `WuiAnyView` instance and that it remains valid for the
334/// duration of this function call.
335#[unsafe(no_mangle)]
336pub unsafe extern "C" fn waterui_view_id(view: *const WuiAnyView) -> WuiTypeId {
337 unsafe {
338 let view = &*view;
339 WuiTypeId::from_runtime(view.type_id(), view.name())
340 }
341}
342
343/// Gets the stretch axis of a view.
344///
345/// Returns the `StretchAxis` that indicates how this view stretches to fill
346/// available space. For native views, this returns the layout behavior defined
347/// by the `NativeView` trait. For non-native views, this will panic.
348///
349/// # Safety
350/// The caller must ensure that `view` is a valid pointer to a properly
351/// initialized `WuiAnyView` instance and that it remains valid for the
352/// duration of this function call.
353#[unsafe(no_mangle)]
354pub unsafe extern "C" fn waterui_view_stretch_axis(
355 view: *const WuiAnyView,
356) -> crate::components::layout::WuiStretchAxis {
357 unsafe { (&*view).stretch_axis().into() }
358}
359
360// WuiTypeId is defined in hot_reload.rs and re-exported from crate root
361
362// ============================================================================
363// WuiStr - UTF-8 string for FFI
364// ============================================================================
365
366// UTF-8 string represented as a byte array
367#[repr(C)]
368pub struct WuiStr(WuiArray<u8>);
369
370impl IntoFFI for Str {
371 type FFI = WuiStr;
372 fn into_ffi(self) -> Self::FFI {
373 WuiStr(WuiArray::new(self))
374 }
375}
376
377impl IntoFFI for &'static str {
378 type FFI = WuiStr;
379 fn into_ffi(self) -> Self::FFI {
380 WuiStr(WuiArray::new(Str::from_static(self)))
381 }
382}
383
384impl IntoRust for WuiStr {
385 type Rust = Str;
386 unsafe fn into_rust(self) -> Self::Rust {
387 let bytes = unsafe { self.0.into_rust() };
388 // Safety: We assume the input bytes are valid UTF-8
389 unsafe { Str::from_utf8_unchecked(bytes) }
390 }
391}
392
393#[unsafe(no_mangle)]
394pub extern "C" fn waterui_empty_anyview() -> *mut WuiAnyView {
395 AnyView::default().into_ffi()
396}
397
398#[repr(C)]
399pub struct WuiMetadata<T> {
400 pub content: *mut WuiAnyView,
401 pub value: T,
402}
403
404impl<T: IntoFFI + MetadataKey> IntoFFI for Metadata<T> {
405 type FFI = WuiMetadata<T::FFI>;
406 fn into_ffi(self) -> Self::FFI {
407 WuiMetadata {
408 content: self.content.into_ffi(),
409 value: self.value.into_ffi(),
410 }
411 }
412}
413
414// ========== Metadata<Environment> FFI ==========
415// Used by WithEnv to pass a new environment to child views
416
417/// Type alias for Metadata<Environment> FFI struct
418/// Layout: { content: *mut WuiAnyView, value: *mut WuiEnv }
419pub type WuiMetadataEnv = WuiMetadata<*mut WuiEnv>;
420
421// Generate waterui_metadata_env_id() and waterui_force_as_metadata_env()
422ffi_metadata!(waterui::Environment, WuiMetadataEnv, env);
423
424// ========== Metadata<Secure> FFI ==========
425// Used to mark views as secure (prevent screenshots)
426
427use waterui::metadata::secure::Secure;
428
429/// C-compatible empty marker struct for Secure metadata.
430/// This is needed because `()` (unit type) is not representable in C.
431#[repr(C)]
432pub struct WuiSecureMarker {
433 /// Placeholder field to ensure struct has valid size in C.
434 /// The actual value is meaningless - Secure is just a marker type.
435 _marker: u8,
436}
437
438impl IntoFFI for Secure {
439 type FFI = WuiSecureMarker;
440 fn into_ffi(self) -> Self::FFI {
441 WuiSecureMarker { _marker: 0 }
442 }
443}
444
445/// Type alias for Metadata<Secure> FFI struct
446/// Layout: { content: *mut WuiAnyView, value: WuiSecureMarker }
447pub type WuiMetadataSecure = WuiMetadata<WuiSecureMarker>;
448
449// Generate waterui_metadata_secure_id() and waterui_force_as_metadata_secure()
450ffi_metadata!(Secure, WuiMetadataSecure, secure);
451
452// ========== Metadata<GestureObserver> FFI ==========
453// Used to attach gesture recognizers to views
454
455use crate::gesture::WuiGestureObserver;
456use waterui::gesture::GestureObserver;
457
458/// Type alias for Metadata<GestureObserver> FFI struct
459pub type WuiMetadataGesture = WuiMetadata<WuiGestureObserver>;
460
461// Generate waterui_metadata_gesture_id() and waterui_force_as_metadata_gesture()
462ffi_metadata!(GestureObserver, WuiMetadataGesture, gesture);
463
464// ========== Metadata<OnEvent> FFI ==========
465// Used to attach lifecycle event handlers (appear/disappear)
466
467use crate::event::WuiOnEvent;
468use waterui_core::event::OnEvent;
469
470/// Type alias for Metadata<OnEvent> FFI struct
471pub type WuiMetadataOnEvent = WuiMetadata<WuiOnEvent>;
472
473// Generate waterui_metadata_on_event_id() and waterui_force_as_metadata_on_event()
474ffi_metadata!(OnEvent, WuiMetadataOnEvent, on_event);
475
476// ========== Metadata<Background> FFI ==========
477// Used to apply background colors or images to views
478
479use crate::color::WuiColor;
480use crate::reactive::WuiComputed;
481use waterui::Color;
482use waterui::background::Background;
483
484/// FFI-safe representation of a background.
485#[repr(C)]
486pub enum WuiBackground {
487 /// A solid color background.
488 Color { color: *mut WuiComputed<Color> },
489 /// An image background.
490 Image { image: *mut WuiComputed<Str> },
491}
492
493impl IntoFFI for Background {
494 type FFI = WuiBackground;
495 fn into_ffi(self) -> Self::FFI {
496 match self {
497 Background::Color(color) => WuiBackground::Color {
498 color: color.into_ffi(),
499 },
500 Background::Image(image) => WuiBackground::Image {
501 image: image.into_ffi(),
502 },
503 _ => unimplemented!(),
504 }
505 }
506}
507
508/// Type alias for Metadata<Background> FFI struct
509pub type WuiMetadataBackground = WuiMetadata<WuiBackground>;
510
511// Generate waterui_metadata_background_id() and waterui_force_as_metadata_background()
512ffi_metadata!(Background, WuiMetadataBackground, background);
513
514// ========== Metadata<ForegroundColor> FFI ==========
515// Used to set foreground/text color for views
516
517use waterui::background::ForegroundColor;
518
519/// FFI-safe representation of a foreground color.
520#[repr(C)]
521pub struct WuiForegroundColor {
522 /// Pointer to the computed color.
523 pub color: *mut WuiComputed<Color>,
524}
525
526impl IntoFFI for ForegroundColor {
527 type FFI = WuiForegroundColor;
528 fn into_ffi(self) -> Self::FFI {
529 WuiForegroundColor {
530 color: self.color.into_ffi(),
531 }
532 }
533}
534
535/// Type alias for Metadata<ForegroundColor> FFI struct
536pub type WuiMetadataForeground = WuiMetadata<WuiForegroundColor>;
537
538// Generate waterui_metadata_foreground_id() and waterui_force_as_metadata_foreground()
539ffi_metadata!(ForegroundColor, WuiMetadataForeground, foreground);
540
541// ========== Metadata<Shadow> FFI ==========
542// Used to apply shadow effects to views
543
544use waterui::style::Shadow;
545
546/// FFI-safe representation of a shadow.
547#[repr(C)]
548pub struct WuiShadow {
549 /// Shadow color (as opaque pointer - needs environment to resolve).
550 pub color: *mut WuiColor,
551 /// Horizontal offset.
552 pub offset_x: f32,
553 /// Vertical offset.
554 pub offset_y: f32,
555 /// Blur radius.
556 pub radius: f32,
557}
558
559impl IntoFFI for Shadow {
560 type FFI = WuiShadow;
561 fn into_ffi(self) -> Self::FFI {
562 WuiShadow {
563 color: self.color.into_ffi(),
564 offset_x: self.offset.x,
565 offset_y: self.offset.y,
566 radius: self.radius,
567 }
568 }
569}
570
571/// Type alias for Metadata<Shadow> FFI struct
572pub type WuiMetadataShadow = WuiMetadata<WuiShadow>;
573
574// Generate waterui_metadata_shadow_id() and waterui_force_as_metadata_shadow()
575ffi_metadata!(Shadow, WuiMetadataShadow, shadow);
576
577// ========== Metadata<Focused> FFI ==========
578// Used to track focus state for views
579
580use crate::reactive::WuiBinding;
581use waterui::component::focus::Focused;
582
583/// FFI-safe representation of focused state.
584#[repr(C)]
585pub struct WuiFocused {
586 /// Binding to the focus state (true = focused).
587 pub binding: *mut WuiBinding<bool>,
588}
589
590impl IntoFFI for Focused {
591 type FFI = WuiFocused;
592 fn into_ffi(self) -> Self::FFI {
593 WuiFocused {
594 binding: self.0.into_ffi(),
595 }
596 }
597}
598
599/// Type alias for Metadata<Focused> FFI struct
600pub type WuiMetadataFocused = WuiMetadata<WuiFocused>;
601
602// Generate waterui_metadata_focused_id() and waterui_force_as_metadata_focused()
603ffi_metadata!(Focused, WuiMetadataFocused, focused);
604
605// ========== Metadata<IgnoreSafeArea> FFI ==========
606// Used to extend views beyond safe area insets
607
608use waterui_layout::IgnoreSafeArea;
609
610/// FFI-safe representation of edge set for safe area.
611#[repr(C)]
612pub struct WuiEdgeSet {
613 /// Ignore safe area on top edge.
614 pub top: bool,
615 /// Ignore safe area on leading edge.
616 pub leading: bool,
617 /// Ignore safe area on bottom edge.
618 pub bottom: bool,
619 /// Ignore safe area on trailing edge.
620 pub trailing: bool,
621}
622
623impl IntoFFI for waterui_layout::EdgeSet {
624 type FFI = WuiEdgeSet;
625 fn into_ffi(self) -> Self::FFI {
626 WuiEdgeSet {
627 top: self.top,
628 leading: self.leading,
629 bottom: self.bottom,
630 trailing: self.trailing,
631 }
632 }
633}
634
635/// FFI-safe representation of IgnoreSafeArea.
636#[repr(C)]
637pub struct WuiIgnoreSafeArea {
638 /// Which edges should ignore safe area.
639 pub edges: WuiEdgeSet,
640}
641
642impl IntoFFI for IgnoreSafeArea {
643 type FFI = WuiIgnoreSafeArea;
644 fn into_ffi(self) -> Self::FFI {
645 WuiIgnoreSafeArea {
646 edges: self.edges.into_ffi(),
647 }
648 }
649}
650
651/// Type alias for Metadata<IgnoreSafeArea> FFI struct
652pub type WuiMetadataIgnoreSafeArea = WuiMetadata<WuiIgnoreSafeArea>;
653
654// Generate waterui_metadata_ignore_safe_area_id() and waterui_force_as_metadata_ignore_safe_area()
655ffi_metadata!(IgnoreSafeArea, WuiMetadataIgnoreSafeArea, ignore_safe_area);
656
657// ========== Metadata<Retain> FFI ==========
658// Used to keep values alive for the lifetime of a view (e.g., watcher guards)
659
660use waterui_core::Retain;
661
662/// FFI-safe representation of Retain metadata.
663/// The actual retained value is opaque - renderers just need to keep it alive.
664#[repr(C)]
665pub struct WuiRetain {
666 /// Opaque pointer to the retained value (Box<dyn Any>).
667 /// This must be kept alive and dropped when the view is disposed.
668 _opaque: *mut (),
669}
670
671impl IntoFFI for Retain {
672 type FFI = WuiRetain;
673 fn into_ffi(self) -> Self::FFI {
674 // Leak the Retain to keep the inner value alive
675 // The native side will call waterui_drop_retain to clean up
676 let boxed = Box::new(self);
677 WuiRetain {
678 _opaque: Box::into_raw(boxed) as *mut (),
679 }
680 }
681}
682
683/// Type alias for Metadata<Retain> FFI struct
684pub type WuiMetadataRetain = WuiMetadata<WuiRetain>;
685
686// Generate waterui_metadata_retain_id() and waterui_force_as_metadata_retain()
687ffi_metadata!(Retain, WuiMetadataRetain, retain);
688
689/// Drops the retained value.
690///
691/// # Safety
692/// The caller must ensure that `retain` is a valid pointer returned from
693/// `waterui_force_as_metadata_retain` and has not been dropped before.
694#[unsafe(no_mangle)]
695pub unsafe extern "C" fn waterui_drop_retain(retain: WuiRetain) {
696 if !retain._opaque.is_null() {
697 unsafe {
698 drop(Box::from_raw(retain._opaque as *mut Retain));
699 }
700 }
701}