#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(not(feature = "std"))]
compile_error!("waterui-ffi requires the `std` feature.");
#[cfg(all(feature = "c-api", feature = "android-jni"))]
compile_error!("waterui-ffi features `c-api` and `android-jni` are mutually exclusive");
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
#[macro_use]
mod macros;
#[cfg(feature = "android-jni")]
pub mod jni;
mod bridge;
mod drawing;
mod events;
mod reactivity;
mod runtime;
mod ty;
pub mod components;
pub use bridge::{action, array, closure, locale};
use core::ptr::null_mut;
pub use drawing::{color, gradient, shape};
pub use events::{animation, cursor, drag_drop, event, gesture};
pub use reactivity::reactive;
pub use runtime::{app, id, safe_area, theme, views, window};
pub use ty::WuiTypeId;
use alloc::boxed::Box;
use executor_core::{init_global_executor, init_local_executor};
#[cfg(target_vendor = "apple")]
use waterkit_audio as _;
use waterui::{AnyView, Str, View};
use waterui_core::{Metadata, Native};
pub use waterui_video;
use waterui_core::metadata::MetadataKey;
use crate::array::WuiArray;
#[inline]
pub const unsafe fn borrow_ffi<'a, T>(ptr: *const T) -> &'a T {
unsafe { &*ptr }
}
#[inline]
pub unsafe fn borrow_ffi_mut<'a, T>(ptr: *mut T) -> &'a mut T {
unsafe { &mut *ptr }
}
#[macro_export]
macro_rules! export {
() => {
const _: () = {
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_init() -> *mut $crate::WuiEnv {
let inspector = unsafe { $crate::__init() };
let mut env = waterui::configure_environment!(waterui::Environment::new());
waterui::inspector::install(&mut env, inspector);
$crate::__configure_browser_environment(&mut env);
$crate::IntoFFI::into_ffi(env)
}
#[unsafe(no_mangle)]
#[allow(unexpected_cfgs)]
pub unsafe extern "C" fn waterui_app(env: *mut $crate::WuiEnv) -> $crate::app::WuiApp {
let env: waterui::Environment = unsafe { $crate::IntoRust::into_rust(env) };
let app: waterui::app::App = app(env);
$crate::IntoFFI::into_ffi(app)
}
#[cfg(target_os = "android")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_android_app(
env: *mut core::ffi::c_void,
) -> $crate::app::WuiAndroidAppHandles {
unsafe { waterui_app(env.cast()) }.into_android_handles()
}
#[cfg(target_os = "android")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_android_init() -> *mut core::ffi::c_void {
unsafe { waterui_init() }.cast()
}
#[cfg(target_os = "android")]
#[unsafe(no_mangle)]
extern "system" fn JNI_OnLoad(
vm: *mut core::ffi::c_void,
_reserved: *mut core::ffi::c_void,
) -> i32 {
unsafe { $crate::__jni_init(vm) }
}
};
};
}
#[allow(
clippy::missing_const_for_fn,
reason = "the body is empty only in the feature configuration being linted; enabling the capability makes it install a realization"
)]
#[doc(hidden)]
#[inline]
pub fn __configure_browser_environment(env: &mut waterui::Environment) {
#[cfg(any(feature = "cef-runtime", feature = "cef-header"))]
components::platform::browser_cef::configure_environment(env);
#[cfg(not(any(feature = "cef-runtime", feature = "cef-header")))]
let _ = env;
}
#[doc(hidden)]
#[cfg(all(target_os = "android", feature = "android-jni"))]
#[inline]
pub unsafe fn __jni_init(vm: *mut core::ffi::c_void) -> i32 {
unsafe { jni::init(vm as *mut jni::jni::sys::JavaVM) }
}
#[cfg(all(target_os = "android", not(feature = "android-jni")))]
compile_error!("waterui-ffi requires the `android-jni` feature when targeting Android");
#[doc(hidden)]
#[cfg(not(target_os = "android"))]
#[inline]
pub const unsafe fn __jni_init(_vm: *mut core::ffi::c_void) -> i32 {
0x0001_0006
}
#[doc(hidden)]
#[inline]
#[must_use]
pub unsafe fn __init() -> Option<waterui::inspector::InspectorRuntime> {
unsafe { __init_impl() }
}
unsafe fn __init_impl() -> Option<waterui::inspector::InspectorRuntime> {
#[cfg(target_os = "android")]
unsafe {
native_executor::android::register_android_main_thread()
.expect("Failed to register Android main thread");
}
let inspector = waterui::inspector::maybe_init_from_env(if cfg!(target_os = "android") {
"android"
} else {
"apple"
});
#[cfg(feature = "std")]
{
std::panic::set_hook(Box::new(|info| {
tracing_panic::panic_hook(info);
}));
init_tracing(
inspector
.as_ref()
.map(waterui::inspector::InspectorRuntime::tracing_layer),
);
}
init_global_executor(native_executor::NativeExecutor::new());
let main_executor = native_executor::NativeMainExecutor::new().expect(
"waterui_init requires a platform main thread; on Android call \
register_android_main_thread on the UI thread first, and on a host that \
owns its own event loop install that loop's LocalExecutor instead",
);
init_local_executor(waterui::task::monitored_local_executor_with_probes(
main_executor,
inspector
.as_ref()
.map(waterui::inspector::InspectorRuntime::runtime_probe),
));
waterui_locale::start_system_locale_listener();
inspector
}
#[cfg(feature = "std")]
fn init_tracing(inspector: Option<waterui::inspector::InspectorLayer>) {
{
#[cfg(target_os = "android")]
{
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
let env_filter = tracing_subscriber::EnvFilter::try_from_default_env()
.or_else(|_| {
tracing_subscriber::EnvFilter::try_new(
"error,wgpu_core=error,wgpu_hal=error,naga=error,jni=error",
)
})
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("error"));
tracing_subscriber::registry()
.with(env_filter)
.with(
tracing_android::layer("WaterUI").expect("Failed to create Android log layer"),
)
.with(inspector)
.init();
}
#[cfg(target_vendor = "apple")]
{
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
let env_filter = tracing_subscriber::EnvFilter::try_from_default_env()
.or_else(|_| {
tracing_subscriber::EnvFilter::try_new(
"error,wgpu_core=error,wgpu_hal=error,naga=error,metal=error",
)
})
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("error"));
tracing_subscriber::registry()
.with(env_filter)
.with(tracing_oslog::OsLogger::new("dev.waterui", "default"))
.with(inspector)
.init();
}
#[cfg(not(any(target_os = "android", target_vendor = "apple")))]
{
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
tracing_subscriber::registry()
.with(tracing_subscriber::EnvFilter::from_default_env())
.with(tracing_subscriber::fmt::layer())
.with(inspector)
.init();
}
}
}
pub trait IntoFFI: 'static {
type FFI: 'static;
fn into_ffi(self) -> Self::FFI;
}
pub trait IntoNullableFFI: 'static {
type FFI: 'static;
fn into_ffi(self) -> Self::FFI;
fn null() -> Self::FFI;
}
impl<T: IntoNullableFFI> IntoFFI for Option<T> {
type FFI = T::FFI;
fn into_ffi(self) -> Self::FFI {
self.map_or_else(T::null, <T as IntoNullableFFI>::into_ffi)
}
}
impl<T: IntoNullableFFI> IntoFFI for T {
type FFI = T::FFI;
fn into_ffi(self) -> Self::FFI {
<T as IntoNullableFFI>::into_ffi(self)
}
}
pub trait InvalidValue {
fn invalid() -> Self;
}
pub trait OpaqueType: 'static {}
impl<T: OpaqueType> IntoNullableFFI for T {
type FFI = *mut T;
fn into_ffi(self) -> Self::FFI {
Box::into_raw(Box::new(self))
}
fn null() -> Self::FFI {
null_mut()
}
}
impl<T: OpaqueType> IntoRust for *mut T {
type Rust = Option<T>;
unsafe fn into_rust(self) -> Self::Rust {
if self.is_null() {
None
} else {
unsafe { Some(*Box::from_raw(self)) }
}
}
}
pub trait IntoRust {
type Rust;
unsafe fn into_rust(self) -> Self::Rust;
}
ffi_safe!(u8, u16, u32, u64, i8, i16, i32, i64, f32, f64, bool);
opaque!(WuiEnv, waterui::Environment, env);
opaque!(WuiAnyView, waterui::AnyView, anyview, any());
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub enum WuiLabelDisplayMode {
Automatic,
TitleAndIcon,
TitleOnly,
IconOnly,
Hidden,
}
impl crate::IntoFFI for waterui_controls::label::LabelDisplayMode {
type FFI = WuiLabelDisplayMode;
fn into_ffi(self) -> Self::FFI {
match self {
Self::Automatic => WuiLabelDisplayMode::Automatic,
Self::TitleAndIcon => WuiLabelDisplayMode::TitleAndIcon,
Self::TitleOnly => WuiLabelDisplayMode::TitleOnly,
Self::IconOnly => WuiLabelDisplayMode::IconOnly,
Self::Hidden => WuiLabelDisplayMode::Hidden,
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct WuiLabel {
pub view: *mut WuiAnyView,
pub accessibility_label: *mut crate::reactive::WuiComputed<waterui_text::styled::StyledStr>,
pub display_mode: WuiLabelDisplayMode,
}
impl crate::IntoFFI for waterui_controls::label::Label {
type FFI = WuiLabel;
fn into_ffi(self) -> Self::FFI {
let accessibility_label = self.accessibility_label();
let display_mode = self.display_mode_preference().into_ffi();
let view = waterui::AnyView::new(self).into_ffi();
WuiLabel {
view,
accessibility_label: accessibility_label.into_ffi(),
display_mode,
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn waterui_anyview_id() -> WuiTypeId {
WuiTypeId::of::<AnyView>()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_clone_env(env: *const WuiEnv) -> *mut WuiEnv {
let env = unsafe { borrow_ffi(env) };
env.0.clone().into_ffi()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_env_disabled(
env: *const WuiEnv,
) -> *mut crate::reactive::WuiComputed<bool> {
let env = unsafe { borrow_ffi(env) };
env.0
.get::<waterui_core::interaction::Disabled>()
.map_or_else(
|| waterui::Computed::constant(false),
|scope| scope.signal().clone(),
)
.into_ffi()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_view_body(
view: *mut WuiAnyView,
env: *mut WuiEnv,
) -> *mut WuiAnyView {
let env = unsafe { borrow_ffi_mut(env) };
let view = unsafe { view.into_rust() };
let body = view.body(env);
AnyView::new(body).into_ffi()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_view_id(view: *const WuiAnyView) -> WuiTypeId {
let view = unsafe { borrow_ffi(view) };
WuiTypeId::from_runtime(view.type_id(), view.name())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_view_stretch_axis(
view: *const WuiAnyView,
) -> crate::components::layout::WuiStretchAxis {
let view = unsafe { borrow_ffi(view) };
view.stretch_axis().into()
}
#[repr(C)]
#[derive(Debug, Default)]
pub struct WuiStr(WuiArray<u8>);
impl IntoFFI for Str {
type FFI = WuiStr;
fn into_ffi(self) -> Self::FFI {
WuiStr(WuiArray::new(self))
}
}
impl IntoFFI for &'static str {
type FFI = WuiStr;
fn into_ffi(self) -> Self::FFI {
WuiStr(WuiArray::new(Str::from_static(self)))
}
}
impl IntoFFI for Option<core::num::NonZeroUsize> {
type FFI = usize;
fn into_ffi(self) -> Self::FFI {
self.map_or(0, core::num::NonZeroUsize::get)
}
}
impl WuiStr {
#[must_use]
pub unsafe fn as_str(&self) -> &str {
unsafe { core::str::from_utf8_unchecked(self.0.as_slice()) }
}
}
#[cfg(feature = "c-api")]
#[unsafe(no_mangle)]
pub extern "C" fn waterui_str_box(value: WuiStr) -> *mut WuiStr {
Box::into_raw(Box::new(value))
}
impl IntoRust for WuiStr {
type Rust = Str;
unsafe fn into_rust(self) -> Self::Rust {
let bytes = self.0.as_slice().to_vec();
self.0.consume();
unsafe { Str::from_utf8_unchecked(bytes) }
}
}
#[repr(C)]
#[derive(Debug)]
pub struct WuiMetadata<T> {
pub content: *mut WuiAnyView,
pub value: T,
}
impl<T: IntoFFI + MetadataKey> IntoFFI for Metadata<T> {
type FFI = WuiMetadata<T::FFI>;
fn into_ffi(self) -> Self::FFI {
WuiMetadata {
content: self.content.into_ffi(),
value: self.value.into_ffi(),
}
}
}
pub type WuiMetadataEnv = WuiMetadata<*mut WuiEnv>;
ffi_metadata!(waterui::Environment, WuiMetadataEnv, env);
use crate::id::WuiId;
use waterui_navigation::{NavigationTransitionDestination, NavigationTransitionSource};
impl IntoFFI for NavigationTransitionSource {
type FFI = WuiId;
fn into_ffi(self) -> Self::FFI {
self.id().into_ffi()
}
}
impl IntoFFI for NavigationTransitionDestination {
type FFI = WuiId;
fn into_ffi(self) -> Self::FFI {
self.id().into_ffi()
}
}
pub type WuiMetadataNavigationTransitionSource = WuiMetadata<WuiId>;
pub type WuiMetadataNavigationTransitionDestination = WuiMetadata<WuiId>;
ffi_metadata!(
NavigationTransitionSource,
WuiMetadataNavigationTransitionSource,
navigation_transition_source
);
ffi_metadata!(
NavigationTransitionDestination,
WuiMetadataNavigationTransitionDestination,
navigation_transition_destination
);
use waterui::metadata::secure::{HighDynamicRange, Secure, StandardDynamicRange};
#[repr(C)]
#[derive(Debug)]
pub struct WuiSecureMarker {
_marker: u8,
}
impl IntoFFI for Secure {
type FFI = WuiSecureMarker;
fn into_ffi(self) -> Self::FFI {
WuiSecureMarker { _marker: 0 }
}
}
pub type WuiMetadataSecure = WuiMetadata<WuiSecureMarker>;
ffi_metadata!(Secure, WuiMetadataSecure, secure);
#[repr(C)]
#[derive(Debug)]
pub struct WuiDynamicRangeMarker {
_marker: u8,
}
impl IntoFFI for StandardDynamicRange {
type FFI = WuiDynamicRangeMarker;
fn into_ffi(self) -> Self::FFI {
WuiDynamicRangeMarker { _marker: 0 }
}
}
impl IntoFFI for HighDynamicRange {
type FFI = WuiDynamicRangeMarker;
fn into_ffi(self) -> Self::FFI {
WuiDynamicRangeMarker { _marker: 0 }
}
}
pub type WuiMetadataStandardDynamicRange = WuiMetadata<WuiDynamicRangeMarker>;
pub type WuiMetadataHighDynamicRange = WuiMetadata<WuiDynamicRangeMarker>;
ffi_metadata!(
StandardDynamicRange,
WuiMetadataStandardDynamicRange,
standard_dynamic_range
);
ffi_metadata!(
HighDynamicRange,
WuiMetadataHighDynamicRange,
high_dynamic_range
);
use crate::gesture::WuiGestureObserver;
use waterui::gesture::GestureObserver;
pub type WuiMetadataGesture = WuiMetadata<WuiGestureObserver>;
ffi_metadata!(GestureObserver, WuiMetadataGesture, gesture);
use crate::event::{WuiLifecycleHook, WuiOnEvent};
use waterui_core::event::{LifeCycleHook, OnEvent};
pub type WuiMetadataLifecycleHook = WuiMetadata<WuiLifecycleHook>;
ffi_metadata!(LifeCycleHook, WuiMetadataLifecycleHook, lifecycle_hook);
pub type WuiMetadataOnEvent = WuiMetadata<WuiOnEvent>;
ffi_metadata!(OnEvent, WuiMetadataOnEvent, on_event);
use crate::cursor::WuiCursor;
use waterui::cursor::Cursor;
pub type WuiMetadataCursor = WuiMetadata<WuiCursor>;
ffi_metadata!(Cursor, WuiMetadataCursor, cursor);
use nami::{Computed, SignalExt as _};
use waterui::accessibility::{
AccessibilityChecked, AccessibilityChildren, AccessibilityHidden, AccessibilityIdentifier,
AccessibilityLabel, AccessibilityRole, AccessibilityState, AccessibilityStateSignal,
};
#[repr(C)]
#[derive(Debug)]
pub struct WuiIgnorableMetadataAccessibilityIdentifier {
pub content: *mut WuiAnyView,
pub identifier: WuiStr,
}
impl IntoFFI for waterui_core::IgnorableMetadata<AccessibilityIdentifier> {
type FFI = WuiIgnorableMetadataAccessibilityIdentifier;
fn into_ffi(self) -> Self::FFI {
WuiIgnorableMetadataAccessibilityIdentifier {
content: self.content.into_ffi(),
identifier: self.value.into_str().into_ffi(),
}
}
}
ffi_ignorable_metadata!(
AccessibilityIdentifier,
WuiIgnorableMetadataAccessibilityIdentifier,
accessibility_identifier
);
#[repr(C)]
#[derive(Debug)]
pub struct WuiIgnorableMetadataAccessibilityLabel {
pub content: *mut WuiAnyView,
pub label: *mut WuiComputed<StyledStr>,
}
impl IntoFFI for waterui_core::IgnorableMetadata<AccessibilityLabel> {
type FFI = WuiIgnorableMetadataAccessibilityLabel;
fn into_ffi(self) -> Self::FFI {
let label = self.value.signal().clone().map(StyledStr::from).computed();
WuiIgnorableMetadataAccessibilityLabel {
content: self.content.into_ffi(),
label: label.into_ffi(),
}
}
}
ffi_ignorable_metadata!(
AccessibilityLabel,
WuiIgnorableMetadataAccessibilityLabel,
accessibility_label
);
fn accessibility_role_code(role: &AccessibilityRole) -> i32 {
match role {
AccessibilityRole::Button => 0,
AccessibilityRole::Link => 1,
AccessibilityRole::Image => 2,
AccessibilityRole::Text => 3,
AccessibilityRole::Header => 4,
AccessibilityRole::Footer => 5,
AccessibilityRole::Navigation => 6,
AccessibilityRole::Main => 7,
AccessibilityRole::Search => 8,
AccessibilityRole::Article => 9,
AccessibilityRole::Section => 10,
AccessibilityRole::List => 11,
AccessibilityRole::ListItem => 12,
AccessibilityRole::Checkbox => 13,
AccessibilityRole::RadioButton => 14,
AccessibilityRole::Switch => 15,
AccessibilityRole::Slider => 16,
AccessibilityRole::ProgressBar => 17,
AccessibilityRole::Tab => 18,
AccessibilityRole::TabList => 19,
AccessibilityRole::TabPanel => 20,
AccessibilityRole::Menu => 21,
AccessibilityRole::MenuItem => 22,
AccessibilityRole::MenuBar => 23,
AccessibilityRole::MenuItemCheckbox => 24,
AccessibilityRole::MenuItemRadio => 25,
AccessibilityRole::Combobox => 26,
AccessibilityRole::Option => 27,
AccessibilityRole::Group => 28,
_ => panic!("unsupported accessibility role in native FFI"),
}
}
#[repr(C)]
#[derive(Debug)]
pub struct WuiIgnorableMetadataAccessibilityValue {
pub content: *mut WuiAnyView,
pub value: i32,
}
impl IntoFFI for waterui_core::IgnorableMetadata<AccessibilityRole> {
type FFI = WuiIgnorableMetadataAccessibilityValue;
fn into_ffi(self) -> Self::FFI {
WuiIgnorableMetadataAccessibilityValue {
content: self.content.into_ffi(),
value: accessibility_role_code(&self.value),
}
}
}
impl IntoFFI for waterui_core::IgnorableMetadata<AccessibilityHidden> {
type FFI = WuiIgnorableMetadataAccessibilityValue;
fn into_ffi(self) -> Self::FFI {
WuiIgnorableMetadataAccessibilityValue {
content: self.content.into_ffi(),
value: i32::from(self.value.is_hidden()),
}
}
}
impl IntoFFI for waterui_core::IgnorableMetadata<AccessibilityChildren> {
type FFI = WuiIgnorableMetadataAccessibilityValue;
fn into_ffi(self) -> Self::FFI {
WuiIgnorableMetadataAccessibilityValue {
content: self.content.into_ffi(),
value: i32::from(self.value.excludes_descendants()),
}
}
}
ffi_ignorable_metadata!(
AccessibilityRole,
WuiIgnorableMetadataAccessibilityValue,
accessibility_role
);
ffi_ignorable_metadata!(
AccessibilityHidden,
WuiIgnorableMetadataAccessibilityValue,
accessibility_hidden
);
ffi_ignorable_metadata!(
AccessibilityChildren,
WuiIgnorableMetadataAccessibilityValue,
accessibility_children
);
#[repr(C)]
#[derive(Debug)]
pub struct WuiAccessibilityState {
pub disabled: *mut WuiComputed<bool>,
pub selected: *mut WuiComputed<bool>,
pub checked: *mut WuiComputed<i32>,
pub expanded: *mut WuiComputed<i32>,
pub busy: *mut WuiComputed<bool>,
pub hidden: *mut WuiComputed<bool>,
}
fn accessibility_state_ffi(state: &Computed<AccessibilityState>) -> WuiAccessibilityState {
let checked = state.map(|state| match state.checked_state() {
None => -1,
Some(AccessibilityChecked::False) => 0,
Some(AccessibilityChecked::True) => 1,
Some(AccessibilityChecked::Mixed) => 2,
});
let expanded = state.map(|state| match state.expanded_state() {
None => -1,
Some(false) => 0,
Some(true) => 1,
});
WuiAccessibilityState {
disabled: state.map(|state| state.is_disabled()).computed().into_ffi(),
selected: state.map(|state| state.is_selected()).computed().into_ffi(),
checked: checked.computed().into_ffi(),
expanded: expanded.computed().into_ffi(),
busy: state.map(|state| state.is_busy()).computed().into_ffi(),
hidden: state.map(|state| state.is_hidden()).computed().into_ffi(),
}
}
#[repr(C)]
#[derive(Debug)]
pub struct WuiIgnorableMetadataAccessibilityState {
pub content: *mut WuiAnyView,
pub state: WuiAccessibilityState,
}
impl IntoFFI for waterui_core::IgnorableMetadata<AccessibilityState> {
type FFI = WuiIgnorableMetadataAccessibilityState;
fn into_ffi(self) -> Self::FFI {
WuiIgnorableMetadataAccessibilityState {
content: self.content.into_ffi(),
state: accessibility_state_ffi(&Computed::constant(self.value)),
}
}
}
impl IntoFFI for waterui_core::IgnorableMetadata<AccessibilityStateSignal> {
type FFI = WuiIgnorableMetadataAccessibilityState;
fn into_ffi(self) -> Self::FFI {
WuiIgnorableMetadataAccessibilityState {
content: self.content.into_ffi(),
state: accessibility_state_ffi(self.value.state()),
}
}
}
ffi_ignorable_metadata!(
AccessibilityState,
WuiIgnorableMetadataAccessibilityState,
accessibility_state
);
ffi_ignorable_metadata!(
AccessibilityStateSignal,
WuiIgnorableMetadataAccessibilityState,
accessibility_state_signal
);
use crate::color::WuiColor;
use crate::reactive::WuiComputed;
use waterui::background::Material;
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub enum WuiMaterial {
UltraThin = 0,
Thin = 1,
Regular = 2,
Thick = 3,
UltraThick = 4,
}
impl IntoFFI for Material {
type FFI = WuiMaterial;
fn into_ffi(self) -> Self::FFI {
match self {
Self::UltraThin => WuiMaterial::UltraThin,
Self::Thin => WuiMaterial::Thin,
Self::Regular => WuiMaterial::Regular,
Self::Thick => WuiMaterial::Thick,
Self::UltraThick => WuiMaterial::UltraThick,
}
}
}
use waterui::style::Shadow;
#[repr(C)]
#[derive(Debug)]
pub struct WuiShadow {
pub color: *mut WuiColor,
pub offset_x: f32,
pub offset_y: f32,
pub radius: f32,
}
impl IntoFFI for Shadow {
type FFI = WuiShadow;
fn into_ffi(self) -> Self::FFI {
WuiShadow {
color: self.color.into_ffi(),
offset_x: self.offset.x,
offset_y: self.offset.y,
radius: self.radius,
}
}
}
pub type WuiMetadataShadow = WuiMetadata<WuiShadow>;
ffi_metadata!(Shadow, WuiMetadataShadow, shadow);
use waterui::border::Border;
#[repr(C)]
#[derive(Debug)]
pub struct WuiBorder {
pub color: *mut WuiColor,
pub width: f32,
pub corner_radius: f32,
pub edges: WuiEdgeSet,
}
impl IntoFFI for Border {
type FFI = WuiBorder;
fn into_ffi(self) -> Self::FFI {
WuiBorder {
color: self.color.into_ffi(),
width: self.width,
corner_radius: self.corner_radius,
edges: self.edges.into_ffi(),
}
}
}
pub type WuiMetadataBorder = WuiMetadata<WuiBorder>;
ffi_metadata!(Border, WuiMetadataBorder, border);
use waterui::style::{Anchor, Offset, Rotation, Scale};
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct WuiAnchor {
pub x: f32,
pub y: f32,
}
impl IntoFFI for Anchor {
type FFI = WuiAnchor;
fn into_ffi(self) -> Self::FFI {
WuiAnchor {
x: self.x,
y: self.y,
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct WuiScale {
pub x: *mut WuiComputed<f32>,
pub y: *mut WuiComputed<f32>,
pub anchor: WuiAnchor,
}
impl IntoFFI for Scale {
type FFI = WuiScale;
fn into_ffi(self) -> Self::FFI {
WuiScale {
x: self.x.into_ffi(),
y: self.y.into_ffi(),
anchor: self.anchor.into_ffi(),
}
}
}
pub type WuiMetadataScale = WuiMetadata<WuiScale>;
ffi_metadata!(Scale, WuiMetadataScale, scale);
#[repr(C)]
#[derive(Debug)]
pub struct WuiRotation {
pub angle: *mut WuiComputed<f32>,
pub anchor: WuiAnchor,
}
impl IntoFFI for Rotation {
type FFI = WuiRotation;
fn into_ffi(self) -> Self::FFI {
WuiRotation {
angle: self.angle.into_ffi(),
anchor: self.anchor.into_ffi(),
}
}
}
pub type WuiMetadataRotation = WuiMetadata<WuiRotation>;
ffi_metadata!(Rotation, WuiMetadataRotation, rotation);
#[repr(C)]
#[derive(Debug)]
pub struct WuiOffset {
pub x: *mut WuiComputed<f32>,
pub y: *mut WuiComputed<f32>,
}
impl IntoFFI for Offset {
type FFI = WuiOffset;
fn into_ffi(self) -> Self::FFI {
WuiOffset {
x: self.x.into_ffi(),
y: self.y.into_ffi(),
}
}
}
pub type WuiMetadataOffset = WuiMetadata<WuiOffset>;
ffi_metadata!(Offset, WuiMetadataOffset, offset);
use waterui::filter::Opacity;
#[repr(C)]
#[derive(Debug)]
pub struct WuiOpacity {
pub value: *mut WuiComputed<f32>,
}
impl IntoFFI for Opacity {
type FFI = WuiOpacity;
fn into_ffi(self) -> Self::FFI {
WuiOpacity {
value: self.value.into_ffi(),
}
}
}
pub type WuiMetadataOpacity = WuiMetadata<WuiOpacity>;
ffi_metadata!(Opacity, WuiMetadataOpacity, opacity);
use crate::reactive::WuiBinding;
use waterui::component::focus::Focused;
#[repr(C)]
#[derive(Debug)]
pub struct WuiFocused {
pub binding: *mut WuiBinding<bool>,
}
impl IntoFFI for Focused {
type FFI = WuiFocused;
fn into_ffi(self) -> Self::FFI {
WuiFocused {
binding: self.0.into_ffi(),
}
}
}
pub type WuiMetadataFocused = WuiMetadata<WuiFocused>;
ffi_metadata!(Focused, WuiMetadataFocused, focused);
use waterui_layout::IgnoreSafeArea;
#[repr(C)]
#[derive(Debug)]
pub struct WuiEdgeSet {
pub top: bool,
pub leading: bool,
pub bottom: bool,
pub trailing: bool,
}
impl IntoFFI for waterui_layout::EdgeSet {
type FFI = WuiEdgeSet;
fn into_ffi(self) -> Self::FFI {
WuiEdgeSet {
top: self.top,
leading: self.leading,
bottom: self.bottom,
trailing: self.trailing,
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct WuiIgnoreSafeArea {
pub edges: WuiEdgeSet,
}
impl IntoFFI for IgnoreSafeArea {
type FFI = WuiIgnoreSafeArea;
fn into_ffi(self) -> Self::FFI {
WuiIgnoreSafeArea {
edges: self.edges.into_ffi(),
}
}
}
pub type WuiMetadataIgnoreSafeArea = WuiMetadata<WuiIgnoreSafeArea>;
ffi_metadata!(IgnoreSafeArea, WuiMetadataIgnoreSafeArea, ignore_safe_area);
use waterui_core::Retain;
#[repr(C)]
#[derive(Debug)]
pub struct WuiRetain {
_opaque: *mut (),
}
#[cfg(feature = "android-jni")]
impl WuiRetain {
pub(crate) fn opaque_ptr(&self) -> *mut () {
self._opaque
}
pub(crate) fn from_ptr(ptr: *mut ()) -> Self {
Self { _opaque: ptr }
}
}
impl IntoFFI for Retain {
type FFI = WuiRetain;
fn into_ffi(self) -> Self::FFI {
let boxed = Box::new(self);
WuiRetain {
_opaque: Box::into_raw(boxed).cast::<()>(),
}
}
}
pub type WuiMetadataRetain = WuiMetadata<WuiRetain>;
ffi_metadata!(Retain, WuiMetadataRetain, retain);
#[unsafe(no_mangle)]
#[expect(
clippy::used_underscore_binding,
reason = "the `_opaque` field name is part of the checked-in C ABI; the underscore signals native code must treat it as opaque"
)]
pub unsafe extern "C" fn waterui_drop_retain(retain: WuiRetain) {
unsafe {
drop(Box::from_raw(retain._opaque.cast::<Retain>()));
}
}
use waterui::shape::{ClipShape, PathCommand};
#[repr(C)]
#[derive(Debug)]
pub enum WuiPathCommand {
MoveTo {
x: f32,
y: f32,
},
LineTo {
x: f32,
y: f32,
},
QuadTo {
cx: f32,
cy: f32,
x: f32,
y: f32,
},
CubicTo {
c1x: f32,
c1y: f32,
c2x: f32,
c2y: f32,
x: f32,
y: f32,
},
Arc {
cx: f32,
cy: f32,
rx: f32,
ry: f32,
start: f32,
sweep: f32,
},
Close,
}
impl IntoFFI for PathCommand {
type FFI = WuiPathCommand;
fn into_ffi(self) -> Self::FFI {
match self {
Self::MoveTo { x, y } => WuiPathCommand::MoveTo { x, y },
Self::LineTo { x, y } => WuiPathCommand::LineTo { x, y },
Self::QuadTo { cx, cy, x, y } => WuiPathCommand::QuadTo { cx, cy, x, y },
Self::CubicTo {
c1x,
c1y,
c2x,
c2y,
x,
y,
} => WuiPathCommand::CubicTo {
c1x,
c1y,
c2x,
c2y,
x,
y,
},
Self::Arc {
cx,
cy,
rx,
ry,
start,
sweep,
} => WuiPathCommand::Arc {
cx,
cy,
rx,
ry,
start,
sweep,
},
Self::Close => WuiPathCommand::Close,
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct WuiClipShape {
pub kind: crate::shape::WuiShapeKind,
pub commands: WuiArray<WuiPathCommand>,
}
impl IntoFFI for ClipShape {
type FFI = WuiClipShape;
fn into_ffi(self) -> Self::FFI {
let commands: alloc::vec::Vec<WuiPathCommand> =
self.commands().iter().map(|cmd| cmd.into_ffi()).collect();
WuiClipShape {
kind: self.kind().into_ffi(),
commands: WuiArray::new(commands),
}
}
}
pub type WuiMetadataClipShape = WuiMetadata<WuiClipShape>;
ffi_metadata!(ClipShape, WuiMetadataClipShape, clip_shape);
use crate::components::icon::WuiSystemIcon;
use crate::components::text::WuiText;
use crate::views::{WuiAnyViews, signal_vec_views};
use waterui::metadata::context_menu::ResolvedContextMenu;
use waterui_controls::menu::{ResolvedMenu, ResolvedMenuItem, Shortcut, ShortcutModifiers};
use waterui_core::handler::SharedAction;
use waterui_icon::SystemIcon;
use waterui_text::styled::StyledStr;
opaque!(WuiSharedAction, SharedAction<()>, shared_action);
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_call_shared_action(
action: *const WuiSharedAction,
env: *const WuiEnv,
) {
let action = unsafe { borrow_ffi(action) }.0.clone();
let env = unsafe { borrow_ffi(env) }.0.clone();
action.call(&env);
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WuiMenuItemTag {
Command = 0,
Divider = 1,
Menu = 2,
}
ffi_safe!(WuiMenuItemTag);
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WuiShortcutModifiers {
pub command: bool,
pub shift: bool,
pub option: bool,
pub control: bool,
}
ffi_safe!(WuiShortcutModifiers);
impl IntoFFI for ShortcutModifiers {
type FFI = WuiShortcutModifiers;
fn into_ffi(self) -> Self::FFI {
WuiShortcutModifiers {
command: self.command(),
shift: self.shift(),
option: self.option(),
control: self.control(),
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct WuiShortcut {
pub key: WuiStr,
pub modifiers: WuiShortcutModifiers,
}
impl IntoFFI for Shortcut {
type FFI = WuiShortcut;
fn into_ffi(self) -> Self::FFI {
WuiShortcut {
key: self.key.into_ffi(),
modifiers: self.modifiers.into_ffi(),
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct WuiMenuItem {
pub tag: WuiMenuItemTag,
pub label: *mut WuiText,
pub icon: *mut WuiSystemIcon,
pub action: *mut WuiSharedAction,
pub disabled: *mut WuiComputed<bool>,
pub selected: *mut WuiComputed<bool>,
pub shortcut: *mut WuiShortcut,
pub items: *mut WuiAnyViews,
}
#[cfg(feature = "c-api")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_menu_item_take_label(label: *mut WuiText) -> WuiText {
unsafe { *Box::from_raw(label) }
}
#[cfg(feature = "c-api")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_menu_item_take_icon(icon: *mut WuiSystemIcon) -> WuiSystemIcon {
unsafe { *Box::from_raw(icon) }
}
#[cfg(feature = "c-api")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_menu_item_take_shortcut(
shortcut: *mut WuiShortcut,
) -> WuiShortcut {
unsafe { *Box::from_raw(shortcut) }
}
#[inline]
fn menu_text(label: waterui_text::TextConfig) -> *mut WuiText {
Box::into_raw(Box::new(label.into_ffi()))
}
#[inline]
fn optional_menu_icon(icon: Option<SystemIcon>) -> *mut WuiSystemIcon {
icon.map_or_else(null_mut, |icon| Box::into_raw(Box::new(icon.into_ffi())))
}
#[inline]
fn optional_shortcut(shortcut: Option<Shortcut>) -> *mut WuiShortcut {
shortcut.map_or_else(null_mut, |shortcut| {
Box::into_raw(Box::new(shortcut.into_ffi()))
})
}
#[doc(hidden)]
#[derive(Debug)]
pub struct ResolvedMenuItemView(ResolvedMenuItem);
waterui_core::raw_view!(ResolvedMenuItemView);
impl IntoFFI for ResolvedMenuItemView {
type FFI = WuiMenuItem;
fn into_ffi(self) -> Self::FFI {
match self.0 {
ResolvedMenuItem::Command(command) => WuiMenuItem {
tag: WuiMenuItemTag::Command,
label: menu_text(command.label),
icon: optional_menu_icon(command.icon),
action: command.action.into_ffi(),
disabled: command.disabled.into_ffi(),
selected: command.selected.into_ffi(),
shortcut: optional_shortcut(command.shortcut),
items: null_mut(),
},
ResolvedMenuItem::Divider => WuiMenuItem {
tag: WuiMenuItemTag::Divider,
label: null_mut(),
icon: null_mut(),
action: null_mut(),
disabled: null_mut(),
selected: null_mut(),
shortcut: null_mut(),
items: null_mut(),
},
ResolvedMenuItem::Menu(menu) => WuiMenuItem {
tag: WuiMenuItemTag::Menu,
label: menu_text(menu.label),
icon: optional_menu_icon(menu.icon),
action: null_mut(),
disabled: null_mut(),
selected: null_mut(),
shortcut: null_mut(),
items: menu_items_views(menu.items),
},
}
}
}
ffi_view!(ResolvedMenuItemView, WuiMenuItem, menu_item);
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum MenuItemIdentity {
Semantic(usize),
Divider(usize),
}
fn menu_item_identity(items: &[ResolvedMenuItem], index: usize) -> MenuItemIdentity {
match &items[index] {
ResolvedMenuItem::Command(command) => MenuItemIdentity::Semantic(command.semantic_id()),
ResolvedMenuItem::Menu(menu) => MenuItemIdentity::Semantic(menu.semantic_id()),
ResolvedMenuItem::Divider => MenuItemIdentity::Divider(
items[..=index]
.iter()
.filter(|item| matches!(item, ResolvedMenuItem::Divider))
.count(),
),
}
}
pub(crate) fn menu_items_views(
items: nami::Computed<alloc::vec::Vec<ResolvedMenuItem>>,
) -> *mut WuiAnyViews {
signal_vec_views(items, menu_item_identity, |item| {
AnyView::new(Native::new(ResolvedMenuItemView(item)))
})
}
#[repr(C)]
#[derive(Debug)]
pub struct WuiContextMenu {
pub items: *mut WuiAnyViews,
}
impl IntoFFI for ResolvedContextMenu {
type FFI = WuiContextMenu;
fn into_ffi(self) -> Self::FFI {
WuiContextMenu {
items: menu_items_views(self.items),
}
}
}
pub type WuiMetadataContextMenu = WuiMetadata<WuiContextMenu>;
ffi_metadata!(ResolvedContextMenu, WuiMetadataContextMenu, context_menu);
#[repr(C)]
#[derive(Debug)]
pub struct WuiMenu {
pub label: *mut WuiAnyView,
pub items: *mut WuiAnyViews,
pub accessibility_label: *mut WuiComputed<StyledStr>,
}
impl IntoFFI for ResolvedMenu {
type FFI = WuiMenu;
fn into_ffi(self) -> Self::FFI {
WuiMenu {
label: self.label.into_ffi(),
items: menu_items_views(self.items),
accessibility_label: self.accessibility_label.into_ffi(),
}
}
}
ffi_view!(ResolvedMenu, WuiMenu, menu);
use crate::drag_drop::{WuiDraggable, WuiDropDestination};
#[cfg(feature = "c-api")]
use waterui::drag_drop::{Draggable, DropDestination};
pub type WuiMetadataDraggable = WuiMetadata<WuiDraggable>;
#[cfg(feature = "c-api")]
ffi_metadata!(Draggable, WuiMetadataDraggable, draggable);
pub type WuiMetadataDropDestination = WuiMetadata<WuiDropDestination>;
#[cfg(feature = "c-api")]
ffi_metadata!(
DropDestination,
WuiMetadataDropDestination,
drop_destination
);
#[cfg(feature = "c-api")]
use waterui::background::MaterialBackground;
#[repr(C)]
#[derive(Debug)]
#[cfg(feature = "c-api")]
pub struct WuiIgnorableMetadataMaterialBackground {
pub content: *mut WuiAnyView,
pub material: WuiMaterial,
}
#[cfg(feature = "c-api")]
impl IntoFFI for waterui_core::IgnorableMetadata<MaterialBackground> {
type FFI = WuiIgnorableMetadataMaterialBackground;
fn into_ffi(self) -> Self::FFI {
WuiIgnorableMetadataMaterialBackground {
content: self.content.into_ffi(),
material: self.value.0.into_ffi(),
}
}
}
#[cfg(feature = "c-api")]
ffi_ignorable_metadata!(
MaterialBackground,
WuiIgnorableMetadataMaterialBackground,
material_background
);
use waterui::interaction::Hittable;
#[repr(C)]
#[derive(Debug)]
pub struct WuiHittable {
pub enabled: *mut WuiComputed<bool>,
}
impl IntoFFI for Hittable {
type FFI = WuiHittable;
fn into_ffi(self) -> Self::FFI {
WuiHittable {
enabled: self.enabled.into_ffi(),
}
}
}
pub type WuiMetadataHittable = WuiMetadata<WuiHittable>;
ffi_metadata!(Hittable, WuiMetadataHittable, hittable);
#[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 waterui_core::handler::SharedAction;
#[test]
fn shared_action_callback_executes() {
let hits = Arc::new(AtomicUsize::new(0));
let hits_for_action = Arc::clone(&hits);
let action_ptr = SharedAction::new(move || {
hits_for_action.fetch_add(1, Ordering::SeqCst);
})
.into_ffi();
let env_ptr = waterui::Environment::new().into_ffi();
unsafe {
waterui_call_shared_action(action_ptr, env_ptr);
}
assert_eq!(hits.load(Ordering::SeqCst), 1);
unsafe {
waterui_drop_shared_action(action_ptr);
let _: waterui::Environment = IntoRust::into_rust(env_ptr);
}
}
#[test]
fn shared_action_survives_reentrant_native_drop_until_callback_returns() {
let action_ptr = Rc::new(Cell::new(core::ptr::null_mut::<WuiSharedAction>()));
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 = SharedAction::new(move || {
unsafe { waterui_drop_shared_action(action_ptr_for_callback.get()) };
callback_finished_for_callback.set(true);
})
.into_ffi();
action_ptr.set(action);
let env = WuiEnv(waterui::Environment::new());
unsafe { waterui_call_shared_action(action, &raw const env) };
assert!(callback_finished.get());
}
}