Skip to main content

frust_native_widgets/api/
mod.rs

1//! The app-facing `api` feature: builders
2//! returning `impl View<State>` compositions over the platform-agnostic
3//! `controls`/`runtime`/`events` machinery `crate` already carries — the
4//! facade-glue half of this crate that sanctions depending on `frust`.
5//!
6//! # Feature-gated, not the crate's default shape
7//!
8//! Everything under this module compiles ONLY behind the default-on
9//! `frust-api` feature — `cargo check -p frust-native-widgets
10//! --no-default-features` must still hold the platform-plugin charter line
11//! (`frust-plugin` + FFI crates only, `docs/ARCHITECTURE.md`'s Module
12//! Structure): no `frust`/`frust-core` dependency reaches the crate at all
13//! with the feature off. See `Cargo.toml`'s comment on why this crate keeps
14//! `frust-core` alongside `frust` under the same gate (a downstream `View`
15//! impl needs `BuildCtx`/`ChangeFlags`, which the `frust` facade does not
16//! re-export).
17//!
18//! # The internal `NativeWidget` trait is still not the public one
19//!
20//! Every builder in [`builders`] is a plain data struct implementing
21//! [`frust_core::Component`] (`frust-core`'s retained-local-state seam) —
22//! never the crate's own `pub(crate)` [`crate::runtime::NativeWidget`] trait,
23//! which stays internal permanently.
24//!
25//! The public form is a different trait entirely:
26//! [`crate::component::NativeComponent`], with `&self` methods, already-typed
27//! `Props` the app constructs directly, no `decode_props` step and no `Result`
28//! returns. The eleven builders here keep riding the internal trait's wire
29//! (`params_json` in, `EventPayload` callbacks out) unchanged — bridging the
30//! public trait onto the same runtime (`crate::component::Bridge`) is what let
31//! that stay true.
32//!
33//! # Two builder families, one slot shape
34//!
35//! The eleven built-in controls above are one family (`native_segmented`/
36//! `native_stepper` on iOS/macOS only and [`native_tab_bar`] on iOS only — a
37//! compile-time refusal banner elsewhere; `native_date_picker` on all three
38//! arms, reporting a [`CivilDate`]); [`native_component`] is
39//! the **generic** one, mounting any registered
40//! [`NativeComponent`](crate::component::NativeComponent) (another plugin's
41//! included — an app crate cannot implement one; see that trait's own doc)
42//! into the same single `platform_view` slot, reusing the eight's
43//! own factory constant, slot counter, sizing rule and refusal placeholder.
44//! It is what closes *define → register → mount*; the eight are deliberately
45//! not rewritten to route through it (see `src/api/mount.rs`'s module doc).
46//!
47//! # One `platform_view` slot per control
48//!
49//! Each builder composes exactly one `frust::platform_view` slot resolving to
50//! this crate's one Android factory
51//! (`dev.frust.nativewidgets.FrustNativeControlFactory`)
52//! — N controls = N slots, within the differ's design envelope (a
53//! shared-container optimization is future work).
54//!
55//! # Native presentations — not a slot
56//!
57//! [`show_native_alert`] / [`show_native_alert_into`] and
58//! [`show_native_sheet`] / [`show_native_sheet_into`] are the app-facing
59//! front door to `crate::present`: an alert or a sheet is an imperative
60//! request answered by one outcome, never a `platform_view` slot (nor does a
61//! sheet host one — its content is a constrained native schema). The `_into`
62//! forms write that outcome into an `RwSignal`, the same events-as-signals
63//! idiom the builders' `on_...` callbacks follow, spawned on
64//! `frust::spawn_local`.
65//!
66//! # Theme ladder L2
67//!
68//! Each builder's `Component::build` reads the active theme
69//! (`use_context::<Theme>()`) and folds it, via [`theme::resolve`], into the
70//! same `params_json` body every other property already rides — see
71//! [`theme`]'s module doc for the mapping table and `crate::android::theme`
72//! for L1 (the night-qualified `Context` control creation builds against).
73
74mod builders;
75mod mount;
76mod present;
77mod signals;
78mod theme;
79
80/// The date value `native_date_picker` shows and reports — defined beside the
81/// control (`crate::controls::date_picker`) because the platform-agnostic
82/// event vocabulary carries it too, and re-exported here as the builder's
83/// public vocabulary.
84pub use crate::controls::date_picker::CivilDate;
85pub use builders::{
86    NativeButtonView, NativeDatePickerStyle, NativeDatePickerView, NativeImageFit, NativeImageView,
87    NativeLabelView, NativeProgressView, NativeSegmentedView, NativeSliderView, NativeSpinnerSize,
88    NativeSpinnerView, NativeStepperView, NativeSwitchView, NativeTabBarView, TabIcon, TabId,
89    TabItem, native_button, native_date_picker, native_image, native_label, native_progress,
90    native_segmented, native_slider, native_spinner, native_stepper, native_switch, native_tab_bar,
91};
92pub use mount::{NativeComponentView, native_component};
93/// The app-facing native-presentation entry points over `crate::present`:
94/// for an alert and for a sheet, an awaitable form and an events-as-signals
95/// form — see the `present` submodule doc.
96pub use present::{
97    show_native_alert, show_native_alert_into, show_native_sheet, show_native_sheet_into,
98};
99
100/// Make sure this build's platform factory exists before the host can look it
101/// up — **optional**: every builder already does this for you.
102///
103/// On iOS the factory a `platform_view` slot resolves to is a Rust
104/// `define_class!` Objective-C class (`crate::apple::factory` — zero Swift),
105/// and objc2 registers such a class with the Objective-C runtime **lazily**,
106/// on the first call from live Rust code. Nothing on the platform side can
107/// trigger that: `FrustViewHost` only ever asks for the class *by name*
108/// (`NSClassFromString`), which returns nil for a class that was never
109/// registered. Every builder in this module therefore forces registration on
110/// the same rebuild that publishes its slot — a whole frame before the host's
111/// post-frame command poll can resolve it — so an app that just calls
112/// `native_button(...)` needs nothing from this function.
113///
114/// Call it anyway if you want registration to happen at a moment you choose
115/// (app startup, say) rather than at first use; it is idempotent, cheap after
116/// the first call, and a no-op on every non-iOS target — Android's factory is
117/// a Kotlin class that exists whether or not Rust has run.
118pub fn ensure_native_factory_registered() {
119    crate::runtime::ensure_platform_factory();
120}
121
122/// The number of native controls this crate's internal runtime currently
123/// retains — the leak bar `registry::Registry::live_count`'s own doc comment
124/// describes ("the number the leak bar every create/dispose cycle must
125/// return to `0`"), surfaced app-side for exactly one reason: a
126/// device-gate harness (a mount/unmount cycler plus a 50-slot stress
127/// toggle, `examples/native-widgets-demo/src/pages/stress.rs`'s GATE
128/// HARNESS section) needs an in-app readout to prove the teardown-retire
129/// path disposes promptly rather than waiting out the differ's
130/// missing-streak backstop (`crate::registry`'s module doc's Idle-deferred
131/// dispose finding).
132///
133/// **Diagnostics/gate accessor, not a supported production API.** It leaks
134/// no registry type, no `NativeWidget` trait, and no handle — just a plain
135/// count. This crate's public-surface decision kept it exactly as it is,
136/// for exactly that reason: it says nothing about the runtime's shape, so
137/// nothing about it constrains the public
138/// [`NativeComponent`](crate::component::NativeComponent) surface. It still
139/// carries no compatibility promise — a future release may narrow, rename or
140/// remove it outright, and a component's live count is included in the number.
141/// Don't build product behavior on it.
142///
143/// Returns `0` on a re-entrant call (the runtime's own thread-local is
144/// already borrowed on this thread — `crate::runtime::with_runtime`'s
145/// documented re-entrancy tolerance) as well as on a platform with no live
146/// runtime at all; either way indistinguishable from "nothing is mounted"
147/// for this accessor's diagnostic purpose.
148pub fn live_slot_count() -> usize {
149    crate::runtime::with_runtime(|runtime| runtime.live_count()).unwrap_or(0)
150}