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}