Skip to main content

swiftui_native/views/
navigation.rs

1//! Navigation.
2
3use super::*;
4use crate::abi;
5use crate::resolve;
6use crate::mangle;
7
8// ── Navigation ──
9
10mangle::modifier_types! {
11    /// `.navigationTitle(_:)` writes a preference and a toolbar key, so it
12    /// nests twice over whatever receiver it is applied to.
13    pub(crate) NAV_TITLE_INNER_T = ty("TransactionalPreferenceTransformModifier").of(vec![ty("NavigationTitleKey")]);
14    /// The outer half of `.navigationTitle(_:)`.
15    pub(crate) NAV_TITLE_OUTER_T = ty("_PreferenceTransformModifier").of(vec![ty("ToolbarKey")]);
16}
17
18mangle::result_types! {
19    /// `NavigationLink<Text, AnyView>`
20    /// `NavigationLink<Text, AnyView>`
21    pub(crate) NAV_LINK_T = ty("NavigationLink").of(vec![ty("Text"), ty("AnyView")]);
22}
23
24
25/// Apply `.navigationTitle(_:)`.
26pub fn navigation_title(view: &ViewHandle, title: &str) -> ViewHandle {
27    unsafe {
28        let ss = abi::swift_string(title);
29        let s0 = u64::from_le_bytes(ss[..8].try_into().unwrap());
30        let s1 = u64::from_le_bytes(ss[8..].try_into().unwrap());
31        let (k0, k1, k2, k3) = localized_string_key(s0, s1);
32        let (inner, _, _) = NAV_TITLE_INNER_T.applied_to(view.meta);
33        let (meta, wt, size) = NAV_TITLE_OUTER_T.applied_to(inner);
34        // x0-x3 = key, then Self.Type and the Self: View witness table
35        let r = abi::call_modifier(resolve::nav_title_fn(), view.value_ptr(), view.meta, view.wt, &[k0, k1, k2, k3], [0.0; 4], size);
36        wrap_owned(r, meta, wt)
37    }
38}
39
40/// Apply `.navigationTitle(_:)` with a `Binding<String>` — an *editable* title.
41///
42/// The title bar becomes a rename field: the user edits it in place and the new
43/// name is written back through the binding. Only the fixed-string overload was
44/// bound, so a document could be shown under a name but never renamed from
45/// where the name appears.
46pub fn navigation_title_bound(
47    view: &ViewHandle,
48    state: &super::BoundState<String>,
49) -> ViewHandle {
50    unsafe {
51        let binding = super::text_binding(state);
52        let b = super::primitives::binding_words(&binding);
53        // `Binding<String>` is four words, the same shape a `TextField` takes.
54        super::opaque_modifier(
55            resolve::nav_title_bound_fn(),
56            resolve::nav_title_bound_oret(),
57            view,
58            &b,
59            [0.0; 4],
60        )
61    }
62}
63
64/// `NavigationLink(_:destination:)`.
65pub fn navigation_link(label: &str, destination: &ViewHandle) -> ViewHandle {
66    unsafe {
67        let ss = abi::swift_string(label);
68        let s0 = u64::from_le_bytes(ss[..8].try_into().unwrap());
69        let s1 = u64::from_le_bytes(ss[8..].try_into().unwrap());
70        let (k0, k1, k2, k3) = localized_string_key(s0, s1);
71
72        let (known_meta, known_wt, known_size) = result_type(&NAV_LINK_T);
73        // `NavigationLink<Label, Destination>`: two parameters, then their
74        // `View` conformances, so the destination is at 1 and its witness at 3.
75        let typed = super::substitute_content(known_meta, 4, 1, 3, destination.meta, destination.wt);
76        let (meta, wt) = typed.unwrap_or((known_meta, known_wt));
77        let size = if typed.is_some() { abi::type_bytes(meta) } else { known_size };
78
79        // `destination` is `@in`, which is *consumed*: whatever x4 points at is
80        // taken over by the callee. Boxed, that was one `AnyView` word; typed,
81        // it is the destination's own bytes, so hand over a copy made through
82        // its value witness rather than the caller's only copy. Both buffers are
83        // declared here because both have to outlive the call.
84        let mut owned: Vec<u8> = match typed {
85            Some(_) => {
86                let vwt = &*swift_runtime_sys::SwiftABI::get_value_witness_table(destination.meta);
87                let mut buf = vec![0u8; abi::type_bytes(destination.meta)];
88                if vwt.size > 0 {
89                    (vwt.initialize_with_copy)(
90                        buf.as_mut_ptr() as *mut c_void,
91                        destination.value_ptr() as *mut c_void,
92                        destination.meta,
93                    );
94                }
95                buf
96            }
97            None => Vec::new(),
98        };
99        let mut boxed_word: u64 =
100            if typed.is_some() { 0 } else { destination.to_anyview_word() };
101        let (dest_ptr, dest_meta, dest_wt) = if typed.is_some() {
102            (owned.as_mut_ptr() as *mut u64, destination.meta, destination.wt)
103        } else {
104            (&mut boxed_word as *mut u64, resolve::anyview_meta(), resolve::anyview_wt())
105        };
106
107        let mut result = vec![0u8; size];
108        // init(_: LocalizedStringKey, destination: D)
109        //   x0-x3 = key, x4 = @in D, x5 = D.Type, x6 = D: View WT, x8 = @out
110        core::arch::asm!("blr {f}", f = in(reg) resolve::nav_link_init(),
111            in("x0") k0, in("x1") k1, in("x2") k2, in("x3") k3,
112            in("x4") dest_ptr,
113            in("x5") dest_meta,
114            in("x6") dest_wt,
115            in("x8") result.as_mut_ptr(),
116            lateout("x7") _,
117            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
118            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
119            lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
120            clobber_abi("C"),
121        );
122        // The callee consumed what x4 pointed at, so this side must not destroy
123        // it — forgotten rather than dropped.
124        core::mem::forget(owned);
125        wrap_owned(result, meta, wt)
126    }
127}
128
129/// `NavigationLink(destination:) { label }` — a link whose label is a view.
130///
131/// [`navigation_link`] takes a string, which is all a plain row needs; a list
132/// row is a whole subtree, and this is the overload that accepts one.
133///
134/// The destination travels `@in` and is *consumed*, as in `navigation_link`, so
135/// what x0 points at is handed over rather than borrowed. The label is an
136/// ordinary `@ViewBuilder` closure, and the four generic words after it are
137/// `Label.Type`, `Destination.Type` and their `View` witnesses — read off the
138/// IR rather than assumed.
139pub fn navigation_link_labelled(label: &ViewHandle, destination: &ViewHandle) -> ViewHandle {
140    unsafe {
141        let (known_meta, known_wt, _) = result_type(&NAV_LINK_T);
142        // `NavigationLink<Label, Destination>`: two parameters, then their
143        // conformances.
144        let args = [label.meta, destination.meta, label.wt, destination.wt];
145        let meta = abi::generic_metadata_from(known_meta, &args);
146        let wt = if meta.is_null() { core::ptr::null() } else { abi::get_view_wt(meta) };
147        if wt.is_null() {
148            // Nothing is lost by the string form's shape: box the label instead.
149            return navigation_link("", destination);
150        }
151        let _ = known_wt;
152
153        let kid = super::Child::new(label, super::Content::Concrete);
154        let handles = kid.element();
155        let payload = super::TuplePayload { children: handles.as_ptr(), count: 1 };
156        let mut ctx = abi::ClosureCtx::new(
157            super::emit_tuple,
158            &payload as *const super::TuplePayload as *mut c_void,
159        );
160
161        // Consumed, so hand over an owned copy made through the value witness.
162        let vwt = &*swift_runtime_sys::SwiftABI::get_value_witness_table(destination.meta);
163        let mut owned = vec![0u8; abi::type_bytes(destination.meta)];
164        if vwt.size > 0 {
165            (vwt.initialize_with_copy)(
166                owned.as_mut_ptr() as *mut c_void,
167                destination.value_ptr() as *mut c_void,
168                destination.meta,
169            );
170        }
171
172        let mut result = vec![0u8; abi::type_bytes(meta)];
173        core::arch::asm!("blr {f}", f = in(reg) resolve::nav_link_label_init(),
174            in("x0") owned.as_mut_ptr(),
175            in("x1") abi::swift_closure_thunk as *const c_void,
176            in("x2") &mut ctx as *mut abi::ClosureCtx,
177            in("x3") label.meta,
178            in("x4") destination.meta,
179            in("x5") label.wt,
180            in("x6") destination.wt,
181            in("x8") result.as_mut_ptr(),
182            lateout("x7") _,
183            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
184            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
185            lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
186            clobber_abi("C"),
187        );
188        core::mem::forget(owned);
189        wrap_owned(result, meta, wt)
190    }
191}
192
193
194/// `(Int) -> some View` — the destination builder, given the value navigated to.
195///
196/// `D` is generic, so the value arrives by *address* rather than in a register,
197/// the same way a `ForEach`'s element does.
198unsafe extern "C" fn emit_destination(out: *mut u8, value: i64, payload: *mut c_void) {
199    unsafe {
200        let builder = &*(payload as *const Box<dyn Fn(i64) -> ViewHandle>);
201        let view = builder(*(value as *const i64));
202        *(out as *mut u64) = view.into_anyview_word();
203    }
204}
205
206/// `.navigationDestination(for: Int.self) { value in … }` — value-based routing.
207///
208/// This is how a `NavigationStack` is driven in modern SwiftUI: rows push a
209/// *value*, and the stack asks this modifier what view that value means. Only
210/// the `isPresented:` overload was bound, which is the older one-destination-
211/// per-flag shape — fine for a single detail sheet, and no use for a stack that
212/// pushes rows.
213///
214/// `Int` is the value type. Anything `Hashable` would do, but a Rust type
215/// cannot be made to conform without synthesising a nominal Swift type, and an
216/// integer identifier is what a Rust-owned model has to hand anyway — the same
217/// reasoning [`super::list_identified`] follows.
218pub fn navigation_destination_for(
219    view: &ViewHandle,
220    destination: impl Fn(i64) -> ViewHandle + 'static,
221) -> ViewHandle {
222    unsafe {
223        let boxed: Box<dyn Fn(i64) -> ViewHandle> = Box::new(destination);
224        let payload = Box::into_raw(Box::new(boxed)) as *mut c_void;
225        let ctx = Box::leak(Box::new(abi::IndexClosureCtx::new(emit_destination, payload)))
226            as *mut abi::IndexClosureCtx;
227
228        let int_meta = resolve::int_meta();
229        let hashable = super::conformance_wt(int_meta, resolve::hashable_proto());
230        assert!(!hashable.is_null(), "Int: Hashable");
231
232        let subst = [view.meta, int_meta, resolve::anyview_meta(),
233                     view.wt, hashable, resolve::anyview_wt()];
234        let (meta, wt, size) = super::opaque_result_type(resolve::nav_dest_for_oret(), &subst);
235        let mut result = vec![0u8; size];
236        let this = view.value_ptr();
237        // navigationDestination<D: Hashable, C: View>(for: D.Type, destination: (D) -> C)
238        //   x0 = the *metatype* argument — `D.self` is a value here, not a
239        //   generic parameter — x1/x2 = the closure, x3 = Self.Type,
240        //   x4 = D.Type, x5 = C.Type, x6 = Self: View WT,
241        //   x7 = D: Hashable WT, [sp] = C: View WT, x8 = @out, x20 = self
242        core::arch::asm!(
243            "sub sp, sp, #16",
244            "str {cw}, [sp]",
245            "blr {f}",
246            "add sp, sp, #16",
247            f = in(reg) resolve::nav_dest_for_fn(),
248            cw = in(reg) subst[5],
249            in("x0") int_meta,
250            in("x1") abi::index_closure_thunk as *const c_void,
251            in("x2") ctx,
252            in("x3") subst[0],
253            in("x4") subst[1],
254            in("x5") subst[2],
255            in("x6") subst[3],
256            in("x7") subst[4],
257            in("x8") result.as_mut_ptr(),
258            in("x20") this,
259            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
260            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
261            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
262        );
263        wrap_owned(result, meta, wt)
264    }
265}