use alloc::boxed::Box;
use nami::SignalExt as _;
use crate::WuiSystemIcon;
use crate::action::WuiAction;
use crate::array::WuiArray;
use crate::closure::ForeignCallbackContext;
use crate::reactive::{WuiBinding, WuiComputed};
use crate::{IntoFFI, WuiAnyView, WuiEnv};
use waterui_core::Str;
use waterui_core::handler::AnyViewBuilder;
use waterui_core::id::Id;
use waterui_graphics::color::ResolvedColor;
use waterui_navigation::tab::{NativeTabStyle, Tab, TabIcon, TabsLayout};
use waterui_navigation::{
Bar, ColumnWidth, CustomNavigationController, NativeNavigationSplitStyle,
NativeNavigationTransition, NavigationController, NavigationDestinationState,
NavigationLinkHint, NavigationSearch, NavigationSplitColumnVisibility, NavigationSplitLayout,
NavigationStack, NavigationTitleDisplayMode, NavigationToolbarItem, NavigationToolbarPlacement,
NavigationTransaction, NavigationView, ToolbarItemIcon, split::NavigationSplitDetailBuilder,
};
use waterui_text::styled::StyledStr;
#[derive(Debug)]
#[repr(C)]
pub struct WuiNavigationView {
pub bar: WuiBar,
pub content: *mut WuiAnyView,
pub state: WuiNavigationDestinationState,
pub transition: WuiNavigationTransition,
}
impl IntoFFI for NavigationView {
type FFI = WuiNavigationView;
fn into_ffi(self) -> Self::FFI {
let transition = self.transition.as_ref().map_or(
WuiNavigationTransition {
kind: WuiNavigationTransitionKind::Inherit,
source_id: 0,
},
|transition| transition.native().into_ffi(),
);
WuiNavigationView {
bar: self.bar.into_ffi(),
content: self.content.into_ffi(),
state: self.state.into_ffi(),
transition,
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct WuiNavigationDestinationState {
pub pop_enabled: *mut WuiComputed<bool>,
pub pop_attempted: *mut WuiAction,
pub appear: *mut WuiAction,
pub disappear: *mut WuiAction,
pub pop: *mut WuiAction,
}
impl IntoFFI for NavigationDestinationState {
type FFI = WuiNavigationDestinationState;
fn into_ffi(self) -> Self::FFI {
WuiNavigationDestinationState {
pop_enabled: self.pop_enabled.into_ffi(),
pop_attempted: self
.pop_attempted
.map_or(core::ptr::null_mut(), IntoFFI::into_ffi),
appear: self.appear.map_or(core::ptr::null_mut(), IntoFFI::into_ffi),
disappear: self
.disappear
.map_or(core::ptr::null_mut(), IntoFFI::into_ffi),
pop: self.pop.map_or(core::ptr::null_mut(), IntoFFI::into_ffi),
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct WuiIgnorableMetadataNavigationLinkHint {
pub content: *mut WuiAnyView,
}
impl IntoFFI for waterui_core::IgnorableMetadata<NavigationLinkHint> {
type FFI = WuiIgnorableMetadataNavigationLinkHint;
fn into_ffi(self) -> Self::FFI {
WuiIgnorableMetadataNavigationLinkHint {
content: self.content.into_ffi(),
}
}
}
ffi_ignorable_metadata!(
NavigationLinkHint,
WuiIgnorableMetadataNavigationLinkHint,
navigation_link_hint
);
#[repr(C)]
#[derive(Debug)]
pub struct WuiNavigationSearch {
pub text: *mut WuiBinding<Str>,
pub prompt: *mut WuiComputed<StyledStr>,
}
impl IntoFFI for NavigationSearch {
type FFI = WuiNavigationSearch;
fn into_ffi(self) -> Self::FFI {
WuiNavigationSearch {
text: self.text.into_ffi(),
prompt: self.prompt.into_config_without_env().content.into_ffi(),
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct WuiOptionalNavigationSearch {
pub has_value: bool,
pub value: WuiNavigationSearch,
}
impl IntoFFI for Option<NavigationSearch> {
type FFI = WuiOptionalNavigationSearch;
fn into_ffi(self) -> Self::FFI {
self.map_or_else(
|| WuiOptionalNavigationSearch {
has_value: false,
value: WuiNavigationSearch {
text: core::ptr::null_mut(),
prompt: core::ptr::null_mut(),
},
},
|search| WuiOptionalNavigationSearch {
has_value: true,
value: search.into_ffi(),
},
)
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WuiNavigationTitleDisplayMode {
Automatic = 0,
Inline = 1,
Medium = 2,
Large = 3,
}
impl IntoFFI for NavigationTitleDisplayMode {
type FFI = WuiNavigationTitleDisplayMode;
fn into_ffi(self) -> Self::FFI {
match self {
Self::Automatic => WuiNavigationTitleDisplayMode::Automatic,
Self::Inline => WuiNavigationTitleDisplayMode::Inline,
Self::Medium => WuiNavigationTitleDisplayMode::Medium,
Self::Large => WuiNavigationTitleDisplayMode::Large,
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct WuiBar {
pub title: *mut WuiAnyView,
pub subtitle: *mut WuiAnyView,
pub toolbar: WuiArray<WuiNavigationToolbarItem>,
pub search: WuiOptionalNavigationSearch,
pub color: *mut WuiComputed<ResolvedColor>,
pub hidden: *mut WuiComputed<bool>,
pub display_mode: WuiNavigationTitleDisplayMode,
}
impl IntoFFI for Bar {
type FFI = WuiBar;
fn into_ffi(self) -> Self::FFI {
let color = self
.color
.as_ref()
.map_or_else(core::ptr::null_mut, |color| {
color.expect_resolved().clone().into_ffi()
});
WuiBar {
title: self.title.into_ffi(),
subtitle: self.subtitle.into_ffi(),
toolbar: self.toolbar.items.into_ffi(),
search: self.search.into_ffi(),
color,
hidden: self.hidden.into_ffi(),
display_mode: self.display_mode.into_ffi(),
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WuiNavigationToolbarPlacement {
Principal = 0,
PrimaryAction = 1,
SecondaryAction = 2,
Confirmation = 3,
Cancellation = 4,
BottomBar = 5,
Status = 6,
TopBarLeading = 7,
TopBarTrailing = 8,
}
impl From<NavigationToolbarPlacement> for WuiNavigationToolbarPlacement {
fn from(value: NavigationToolbarPlacement) -> Self {
match value {
NavigationToolbarPlacement::Principal => Self::Principal,
NavigationToolbarPlacement::PrimaryAction => Self::PrimaryAction,
NavigationToolbarPlacement::SecondaryAction => Self::SecondaryAction,
NavigationToolbarPlacement::Confirmation => Self::Confirmation,
NavigationToolbarPlacement::Cancellation => Self::Cancellation,
NavigationToolbarPlacement::BottomBar => Self::BottomBar,
NavigationToolbarPlacement::Status => Self::Status,
NavigationToolbarPlacement::TopBarLeading => Self::TopBarLeading,
NavigationToolbarPlacement::TopBarTrailing => Self::TopBarTrailing,
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct WuiNavigationToolbarItem {
pub placement: WuiNavigationToolbarPlacement,
pub content: *mut WuiAnyView,
pub title: *mut WuiComputed<StyledStr>,
pub system_icon: *mut WuiSystemIcon,
pub icon: *mut WuiAnyView,
}
impl IntoFFI for NavigationToolbarItem {
type FFI = WuiNavigationToolbarItem;
fn into_ffi(self) -> Self::FFI {
let (system_icon, icon) = match self.icon {
Some(ToolbarItemIcon::System(icon)) => (
Box::into_raw(Box::new(icon.into_ffi())),
core::ptr::null_mut(),
),
Some(ToolbarItemIcon::View(view)) => (core::ptr::null_mut(), view.build().into_ffi()),
None => (core::ptr::null_mut(), core::ptr::null_mut()),
};
WuiNavigationToolbarItem {
placement: self.placement.into(),
content: self.content.into_ffi(),
title: self
.title
.map_or(core::ptr::null_mut(), |title| title.content().into_ffi()),
system_icon,
icon,
}
}
}
ffi_view!(NavigationView, WuiNavigationView, navigation_view);
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WuiNavigationTransitionKind {
Automatic = 0,
Fade = 1,
Zoom = 2,
None = 3,
Custom = 4,
Inherit = 5,
}
#[repr(C)]
#[derive(Debug)]
pub struct WuiNavigationTransition {
pub kind: WuiNavigationTransitionKind,
pub source_id: i32,
}
impl IntoFFI for NativeNavigationTransition {
type FFI = WuiNavigationTransition;
fn into_ffi(self) -> Self::FFI {
match self {
Self::Automatic => WuiNavigationTransition {
kind: WuiNavigationTransitionKind::Automatic,
source_id: 0,
},
Self::Fade => WuiNavigationTransition {
kind: WuiNavigationTransitionKind::Fade,
source_id: 0,
},
Self::Zoom(source) => WuiNavigationTransition {
kind: WuiNavigationTransitionKind::Zoom,
source_id: i32::from(source),
},
Self::None => WuiNavigationTransition {
kind: WuiNavigationTransitionKind::None,
source_id: 0,
},
Self::Custom => WuiNavigationTransition {
kind: WuiNavigationTransitionKind::Custom,
source_id: 0,
},
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct WuiNavigationStack {
pub root: *mut WuiAnyView,
pub transition: WuiNavigationTransition,
}
impl IntoFFI for NavigationStack<(), ()> {
type FFI = WuiNavigationStack;
fn into_ffi(self) -> Self::FFI {
let transition = self.transition_style().native().into_ffi();
WuiNavigationStack {
root: self.into_inner().into_ffi(),
transition,
}
}
}
ffi_view!(NavigationStack<(),()>, WuiNavigationStack, navigation_stack);
unsafe fn resolve_navigation_stack_root(
root: *mut WuiAnyView,
env: *const WuiEnv,
) -> *mut WuiAnyView {
let root: waterui::AnyView = unsafe { crate::IntoRust::into_rust(root) };
let env = unsafe { crate::borrow_ffi(env) };
let root = waterui_navigation::resolve_navigation_root(root, &env.0);
waterui::AnyView::new(waterui_core::Native::new(root)).into_ffi()
}
#[cfg(feature = "c-api")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_navigation_stack_root(
root: *mut WuiAnyView,
env: *const WuiEnv,
) -> *mut WuiAnyView {
unsafe { resolve_navigation_stack_root(root, env) }
}
#[cfg(feature = "android-jni")]
#[unsafe(no_mangle)]
unsafe extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_navigationStackRoot<'local>(
_env: crate::jni::JNIEnv<'local>,
_class: crate::jni::JClass<'local>,
root_ptr: crate::jni::jlong,
env_ptr: crate::jni::jlong,
) -> crate::jni::jlong {
use crate::jni::convert::{jlong_to_ptr, jlong_to_ptr_mut};
let root = unsafe { jlong_to_ptr_mut(root_ptr) };
let env = unsafe { jlong_to_ptr(env_ptr) };
let resolved = unsafe { resolve_navigation_stack_root(root, env) };
resolved as crate::jni::jlong
}
#[repr(C)]
#[derive(Debug)]
pub struct WuiNavigationSplitLayout {
pub sidebar: *mut WuiAnyView,
pub placeholder: *mut WuiAnyView,
pub primary_selection: *mut WuiBinding<i32>,
pub content: *mut WuiNavigationSplitDetail,
pub secondary_selection: *mut WuiBinding<i32>,
pub detail: *mut WuiNavigationSplitDetail,
pub column_visibility: *mut WuiComputed<i32>,
pub sidebar_width: WuiNavigationColumnWidth,
pub style: WuiNavigationSplitStyle,
}
#[repr(C)]
#[derive(Debug)]
pub struct WuiNavigationColumnWidth {
pub min: f32,
pub ideal: f32,
pub max: f32,
}
impl From<ColumnWidth> for WuiNavigationColumnWidth {
fn from(value: ColumnWidth) -> Self {
Self {
min: value.min(),
ideal: value.ideal(),
max: value.max(),
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WuiNavigationSplitStyle {
Automatic = 0,
Balanced = 1,
ProminentDetail = 2,
}
impl From<NativeNavigationSplitStyle> for WuiNavigationSplitStyle {
fn from(value: NativeNavigationSplitStyle) -> Self {
match value {
NativeNavigationSplitStyle::Automatic => Self::Automatic,
NativeNavigationSplitStyle::Balanced => Self::Balanced,
NativeNavigationSplitStyle::ProminentDetail => Self::ProminentDetail,
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct WuiNavigationSplitDetail {
_private: [u8; 0],
}
impl crate::IntoFFI for NavigationSplitDetailBuilder {
type FFI = *mut WuiNavigationSplitDetail;
fn into_ffi(self) -> Self::FFI {
Box::into_raw(Box::new(self)).cast::<WuiNavigationSplitDetail>()
}
}
impl crate::IntoRust for *mut WuiNavigationSplitDetail {
type Rust = NavigationSplitDetailBuilder;
unsafe fn into_rust(self) -> Self::Rust {
unsafe { *Box::from_raw(self.cast::<NavigationSplitDetailBuilder>()) }
}
}
#[cfg(feature = "c-api")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_drop_split_navigation_detail(
value: *mut WuiNavigationSplitDetail,
) {
let _ = unsafe { crate::IntoRust::into_rust(value) };
}
#[cfg(feature = "android-jni")]
#[unsafe(no_mangle)]
unsafe extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_dropSplitNavigationDetail<
'local,
>(
_env: crate::jni::JNIEnv<'local>,
_class: crate::jni::JClass<'local>,
ptr: crate::jni::jlong,
) {
use crate::jni::convert::jlong_to_ptr_mut;
let ptr: *mut WuiNavigationSplitDetail = unsafe { jlong_to_ptr_mut(ptr) };
let _ = unsafe { crate::IntoRust::into_rust(ptr) };
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_split_navigation_detail_content(
handler: *mut WuiNavigationSplitDetail,
selected: crate::id::WuiId,
env: *const WuiEnv,
) -> WuiNavigationView {
let handler = unsafe { crate::borrow_ffi(handler as *const NavigationSplitDetailBuilder) };
let selected = unsafe { crate::IntoRust::into_rust(selected) };
let env = unsafe { crate::borrow_ffi(env) };
let mut view = handler.build(selected);
view.resolve_native_fields(&env.0);
IntoFFI::into_ffi(view)
}
impl IntoFFI for NavigationSplitLayout {
type FFI = WuiNavigationSplitLayout;
fn into_ffi(self) -> Self::FFI {
let (
sidebar,
placeholder,
primary_selection,
content,
secondary_selection,
detail,
column_visibility,
sidebar_width,
style,
) = self.into_parts();
let primary_selection = optional_id_binding(&primary_selection).into_ffi();
let secondary_selection = secondary_selection
.as_ref()
.map_or(core::ptr::null_mut(), |selection| {
optional_id_binding(selection).into_ffi()
});
let column_visibility = column_visibility
.map(|visibility| match visibility {
NavigationSplitColumnVisibility::Automatic => 0,
NavigationSplitColumnVisibility::All => 1,
NavigationSplitColumnVisibility::DoubleColumn => 2,
NavigationSplitColumnVisibility::DetailOnly => 3,
})
.computed()
.into_ffi();
WuiNavigationSplitLayout {
sidebar: sidebar.build().into_ffi(),
placeholder: placeholder.build().into_ffi(),
primary_selection,
content: content.map_or(core::ptr::null_mut(), IntoFFI::into_ffi),
secondary_selection,
detail: detail.into_ffi(),
column_visibility,
sidebar_width: sidebar_width.into(),
style: style.into(),
}
}
}
fn optional_id_binding(selection: &nami::Binding<Option<Id>>) -> WuiBinding<i32> {
WuiBinding(nami::Binding::mapping(
selection,
|value| value.map_or(0, i32::from),
|binding, value| {
binding.set(core::num::NonZeroI32::new(value).map(waterui_core::id::Id::from));
},
))
}
ffi_view!(
NavigationSplitLayout,
WuiNavigationSplitLayout,
split_navigation_container
);
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WuiTabStyle {
Automatic = 0,
TabBar = 1,
Sidebar = 2,
}
impl From<NativeTabStyle> for WuiTabStyle {
fn from(style: NativeTabStyle) -> Self {
match style {
NativeTabStyle::Automatic => Self::Automatic,
NativeTabStyle::TabBar => Self::TabBar,
NativeTabStyle::Sidebar => Self::Sidebar,
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct WuiTabs {
pub selection: *mut WuiBinding<Id>,
pub tabs: WuiArray<WuiTab>,
pub style: WuiTabStyle,
}
opaque!(WuiTabContent, AnyViewBuilder<NavigationView>, tab_content);
#[repr(C)]
#[derive(Debug)]
pub struct WuiTab {
pub id: u64,
pub label: *mut WuiAnyView,
pub content: *mut WuiTabContent,
pub badge: *mut WuiComputed<i32>,
pub enabled: *mut WuiComputed<bool>,
pub system_icon: *mut WuiSystemIcon,
pub icon: *mut WuiAnyView,
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_tab_content(
handler: *mut WuiTabContent,
env: *const WuiEnv,
) -> WuiNavigationView {
unsafe {
let handler = crate::borrow_ffi(handler);
let env = crate::borrow_ffi(env);
let mut view = handler.build();
view.resolve_native_fields(&env.0);
IntoFFI::into_ffi(view)
}
}
impl IntoFFI for Tab<Id> {
type FFI = WuiTab;
fn into_ffi(self) -> Self::FFI {
let (system_icon, icon) = match self.icon {
Some(TabIcon::System(icon)) => (
Box::into_raw(Box::new(icon.into_ffi())),
core::ptr::null_mut(),
),
Some(TabIcon::View(view)) => (core::ptr::null_mut(), view.build().into_ffi()),
None => (core::ptr::null_mut(), core::ptr::null_mut()),
};
let id_i32 = i32::from(self.id);
let id = u64::try_from(id_i32)
.expect("tab id must be positive when converting to FFI u64 identifier");
WuiTab {
id,
label: self.label.into_ffi(),
content: self.content.into_ffi(),
badge: self.badge.map_or(core::ptr::null_mut(), IntoFFI::into_ffi),
enabled: self.enabled.into_ffi(),
system_icon,
icon,
}
}
}
impl IntoFFI for TabsLayout {
type FFI = WuiTabs;
fn into_ffi(self) -> Self::FFI {
WuiTabs {
selection: self.selection.into_ffi(),
tabs: self.tabs.into_ffi(),
style: self.style.into(),
}
}
}
ffi_view!(TabsLayout, WuiTabs, tabs);
struct ForeignNavigationController {
context: ForeignCallbackContext,
apply: unsafe extern "C" fn(*mut (), WuiNavigationTransaction),
}
#[repr(C)]
#[derive(Debug)]
pub struct WuiNavigationTransaction {
pub id: u64,
pub retained_prefix: usize,
pub removed: usize,
pub inserted: WuiArray<WuiNavigationView>,
}
#[expect(
clippy::non_send_fields_in_send_ty,
reason = "the foreign callback context is created, invoked, and dropped on the platform main thread; `Send` is asserted only to satisfy the controller trait bound"
)]
unsafe impl Send for ForeignNavigationController {}
impl CustomNavigationController for ForeignNavigationController {
fn apply(&mut self, transaction: NavigationTransaction) {
let NavigationTransaction {
id,
retained_prefix,
removed,
inserted,
} = transaction;
let inserted = inserted
.into_iter()
.map(|builder| builder.build())
.map(IntoFFI::into_ffi)
.collect::<alloc::vec::Vec<_>>();
unsafe {
(self.apply)(
self.context.data(),
WuiNavigationTransaction {
id,
retained_prefix,
removed,
inserted: WuiArray::new(inserted),
},
);
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_env_install_navigation_controller(
env: *mut WuiEnv,
context: *mut (),
apply: unsafe extern "C" fn(*mut (), WuiNavigationTransaction),
drop_context: unsafe extern "C" fn(*mut ()),
) {
let env = unsafe { crate::borrow_ffi_mut(env) };
let controller = ForeignNavigationController {
context: unsafe { ForeignCallbackContext::new(context, drop_context) },
apply,
};
env.insert(NavigationController::new(controller));
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_env_has_navigation_controller(env: *const WuiEnv) -> bool {
unsafe {
let env = crate::borrow_ffi(env);
env.get::<NavigationController>().is_some()
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_navigation_pop(env: *const WuiEnv) {
unsafe {
let env = crate::borrow_ffi(env);
if let Some(controller) = env.get::<NavigationController>() {
controller.request_pop(1);
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_navigation_complete_native_pop(env: *const WuiEnv, count: usize) {
let env = unsafe { crate::borrow_ffi(env) };
let controller = env
.get::<NavigationController>()
.expect("native navigation pop completed without an installed controller");
controller.complete_native_pop(count);
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_navigation_transition_completed(
env: *const WuiEnv,
id: u64,
) -> bool {
let env = unsafe { crate::borrow_ffi(env) };
let controller = env
.get::<NavigationController>()
.expect("navigation transition completed without an installed controller");
controller.transition_completed(id)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_navigation_transition_cancelled(
env: *const WuiEnv,
id: u64,
) -> bool {
let env = unsafe { crate::borrow_ffi(env) };
let controller = env
.get::<NavigationController>()
.expect("navigation transition cancelled without an installed controller");
controller.transition_cancelled(id)
}