Skip to main content

swiftui_native/views/
toolbar.rs

1//! Toolbar.
2
3use super::*;
4use crate::abi;
5use crate::resolve;
6use core::ffi::c_void;
7use crate::mangle;
8
9// ── Toolbar ──
10
11mangle::result_types! {
12    /// `ToolbarItem<(), AnyView>`
13    /// `ToolbarItem<(), AnyView>`
14    pub(crate) TOOLBAR_ITEM_T = ty("ToolbarItem").of(vec![Type::Void, ty("AnyView")]);
15    /// `TupleToolbarContent<ToolbarItem<(), AnyView>>`
16    /// `TupleToolbarContent<ToolbarItem<(), AnyView>>`
17    pub(crate) TOOLBAR_TUPLE_T = ty("TupleToolbarContent").of(vec![ty("ToolbarItem").of(vec![Type::Void, ty("AnyView")])]);
18    /// `ToolbarItem<String, AnyView>` — the customizable item.
19    /// `ToolbarItem<String, AnyView>`
20    pub(crate) TOOLBAR_ITEM_ID_T = ty("ToolbarItem").of(vec![Type::Verbatim("SS".into()), ty("AnyView")]);
21    /// `TupleToolbarContent<ToolbarItem<String, AnyView>>`
22    /// `TupleToolbarContent<ToolbarItem<String, AnyView>>`
23    pub(crate) TOOLBAR_TUPLE_ID_T = ty("TupleToolbarContent").of(vec![ty("ToolbarItem").of(vec![Type::Verbatim("SS".into()), ty("AnyView")])]);
24}
25mangle::modifier_types! {
26    /// `.toolbar(content:)`
27    /// `ModifiedContent<AnyView, ToolbarModifier<(), TupleToolbarContent<ToolbarItem<(), AnyView>>>>`
28    pub(crate) TOOLBAR_T = ty("ToolbarModifier").of(vec![Type::Void, ty("TupleToolbarContent").of(vec![ty("ToolbarItem").of(vec![Type::Void, ty("AnyView")])])]);
29}
30
31
32
33swift_statics! {
34    /// `SwiftUI.ToolbarItemPlacement` — where a toolbar item goes.
35    ///
36    /// The cases are the type's documented static properties, on the platforms
37    /// this crate targets. The iOS-only spellings — `navigationBarLeading`,
38    /// `bottomBar`, `topBarTrailing` and the rest — are
39    /// `@available(macOS, unavailable)` and are left out rather than bound to
40    /// trap; `examples/statics` is what says which those are.
41    pub enum ToolbarItemPlacement {
42        Automatic = c"$s7SwiftUI20ToolbarItemPlacementV9automaticACvgZ",
43        CancellationAction = c"$s7SwiftUI20ToolbarItemPlacementV18cancellationActionACvgZ",
44        ConfirmationAction = c"$s7SwiftUI20ToolbarItemPlacementV18confirmationActionACvgZ",
45        DestructiveAction = c"$s7SwiftUI20ToolbarItemPlacementV17destructiveActionACvgZ",
46        Keyboard = c"$s7SwiftUI20ToolbarItemPlacementV8keyboardACvgZ",
47        Navigation = c"$s7SwiftUI20ToolbarItemPlacementV10navigationACvgZ",
48        PrimaryAction = c"$s7SwiftUI20ToolbarItemPlacementV13primaryActionACvgZ",
49        Principal = c"$s7SwiftUI20ToolbarItemPlacementV9principalACvgZ",
50        SecondaryAction = c"$s7SwiftUI20ToolbarItemPlacementV15secondaryActionACvgZ",
51        Status = c"$s7SwiftUI20ToolbarItemPlacementV6statusACvgZ",
52    }
53}
54
55/// A closure body that writes back a value built in advance.
56#[repr(C)]
57pub(crate) struct BytesPayload {
58    pub(crate) data: *const u8,
59    pub(crate) len: usize,
60}
61
62pub(crate) unsafe extern "C" fn emit_bytes(out: *mut u8, payload: *mut c_void) {
63    let p = unsafe { &*(payload as *const BytesPayload) };
64    unsafe { core::ptr::copy_nonoverlapping(p.data, out, p.len) };
65}
66
67/// Apply `.toolbar { ToolbarItem { content } }`.
68///
69/// The toolbar takes a `ToolbarContentBuilder` closure, not a `ViewBuilder` one,
70/// and unlike `ViewBuilder` it does *not* collapse a single element — one item
71/// still becomes a `TupleToolbarContent`. So this is three chained calls: build
72/// the item, wrap it through `buildBlock`, then hand the result to `toolbar`.
73pub fn toolbar(view: &ViewHandle, content: &ViewHandle) -> ViewHandle {
74    toolbar_placed(view, ToolbarItemPlacement::Automatic, content)
75}
76
77/// Apply `.toolbar { ToolbarItem(placement:) { content } }`.
78///
79/// `toolbar` above is this with `.automatic`, which is what the placement
80/// argument defaults to in Swift.
81pub fn toolbar_placed(
82    view: &ViewHandle,
83    placement: ToolbarItemPlacement,
84    content: &ViewHandle,
85) -> ViewHandle {
86    unsafe {
87        // `ToolbarItemPlacement` is 41 bytes, so indirect throughout.
88        let placement_meta = abi::metadata0(resolve::toolbar_placement_ma());
89        let mut placement =
90            static_getter_indirect(placement.resolved(), placement_meta, 0);
91
92        // The item's content closure. It is retained by the toolbar, so leak it.
93        let item_ctx = Box::leak(Box::new(abi::ClosureCtx::new(
94            emit_tuple,
95            Child::leak(content, Content::Erased),
96        ))) as *mut abi::ClosureCtx;
97
98        let (item_meta, _, item_size) = {
99            let m = abi::resolve_type_by_mangled_name(&TOOLBAR_ITEM_T);
100            assert!(!m.is_null(), "ToolbarItem<(), AnyView> metadata");
101            (m, (), abi::type_bytes(m))
102        };
103        let mut item = vec![0u8; item_size];
104        // ToolbarItem.init(placement:content:)
105        //   x0 = @in placement, x1 = content fn, x2 = content ctx,
106        //   x3 = B.Type, x4 = B: View WT, x8 = @out
107        core::arch::asm!("blr {f}", f = in(reg) resolve::toolbar_item_init(),
108            in("x0") placement.as_mut_ptr(),
109            in("x1") abi::swift_closure_thunk as *const c_void,
110            in("x2") item_ctx,
111            in("x3") resolve::anyview_meta(),
112            in("x4") resolve::anyview_wt(),
113            in("x8") item.as_mut_ptr(),
114            lateout("x5") _, lateout("x6") _, lateout("x7") _,
115            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
116            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
117            lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
118            clobber_abi("C"),
119        );
120
121        let item_tc_wt =
122            abi::get_view_wt_for_proto(item_meta, resolve::toolbar_content_proto());
123        assert!(!item_tc_wt.is_null(), "ToolbarItem: ToolbarContent");
124
125        // ToolbarContentBuilder.buildBlock(item)
126        //   x0 = @in C, x1 = C.Type, x2 = C: ToolbarContent WT, x8 = @out
127        let tuple_meta = abi::resolve_type_by_mangled_name(&TOOLBAR_TUPLE_T);
128        assert!(!tuple_meta.is_null(), "TupleToolbarContent metadata");
129        let mut tuple = vec![0u8; abi::type_bytes(tuple_meta)];
130        core::arch::asm!("blr {f}", f = in(reg) resolve::toolbar_build_block(),
131            in("x0") item.as_mut_ptr(),
132            in("x1") item_meta,
133            in("x2") item_tc_wt,
134            in("x8") tuple.as_mut_ptr(),
135            lateout("x3") _, lateout("x4") _, lateout("x5") _, lateout("x6") _,
136            lateout("x7") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
137            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
138            lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x20") _,
139            lateout("x21") _, clobber_abi("C"),
140        );
141
142        let tuple_tc_wt =
143            abi::get_view_wt_for_proto(tuple_meta, resolve::toolbar_content_proto());
144        assert!(!tuple_tc_wt.is_null(), "TupleToolbarContent: ToolbarContent");
145
146        // The toolbar's own content closure just hands back the tuple we built.
147        let leaked = Box::leak(tuple.into_boxed_slice());
148        let bytes = Box::leak(Box::new(BytesPayload {
149            data: leaked.as_ptr(),
150            len: leaked.len(),
151        }));
152        let ctx = Box::leak(Box::new(abi::ClosureCtx::new(
153            emit_bytes,
154            bytes as *mut BytesPayload as *mut c_void,
155        ))) as *mut abi::ClosureCtx;
156
157        let (_, _, size) = TOOLBAR_T.applied_to(view.meta);
158        let mut result = vec![0u8; size];
159        let this = view.value_ptr();
160        // toolbar<T: ToolbarContent>(content: () -> T)
161        //   x0 = closure fn, x1 = closure ctx, x2 = Self.Type, x3 = T.Type,
162        //   x4 = Self: View WT, x5 = T: ToolbarContent WT, x8 = @out, x20 = self
163        core::arch::asm!("blr {f}", f = in(reg) resolve::toolbar_fn(),
164            in("x0") abi::swift_closure_thunk as *const c_void,
165            in("x1") ctx,
166            in("x2") view.meta,
167            in("x3") tuple_meta,
168            in("x4") content.wt,
169            in("x5") tuple_tc_wt,
170            in("x8") result.as_mut_ptr(),
171            in("x20") this,
172            lateout("x6") _, lateout("x7") _,
173            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
174            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
175            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
176        );
177        wrap_modified(result, &TOOLBAR_T, view)
178    }
179}
180
181/// Apply `.toolbar(id:) { ToolbarItem(id:) { content } }` — a toolbar the user
182/// can rearrange, whose arrangement the system persists under `toolbar_id`.
183///
184/// Customizability is a property of the item's *type*, not a flag on the call:
185/// the conformance is `extension ToolbarItem: CustomizableToolbarContent where
186/// ID == String`, so an item built by [`toolbar`]'s `init(placement:content:)`
187/// has `ID == ()` and does not conform. This builds `ToolbarItem<String,
188/// AnyView>` through the id-taking initialiser instead, and every witness below
189/// is looked up against `CustomizableToolbarContent` rather than
190/// `ToolbarContent` — `TupleToolbarContent` conforms conditionally, on its
191/// element conforming, which is why the item has to be the customizable one
192/// first.
193///
194/// `shows_by_default` is whether the item appears before the user has
195/// customized anything; one set to `false` exists only in the customization
196/// palette until it is dragged in.
197pub fn toolbar_customizable(
198    view: &ViewHandle,
199    toolbar_id: &str,
200    item_id: &str,
201    shows_by_default: bool,
202    content: &ViewHandle,
203) -> ViewHandle {
204    toolbar_customizable_placed(
205        view,
206        toolbar_id,
207        item_id,
208        ToolbarItemPlacement::Automatic,
209        shows_by_default,
210        content,
211    )
212}
213
214/// The same, with the item placed somewhere other than `.automatic`.
215pub fn toolbar_customizable_placed(
216    view: &ViewHandle,
217    toolbar_id: &str,
218    item_id: &str,
219    placement: ToolbarItemPlacement,
220    shows_by_default: bool,
221    content: &ViewHandle,
222) -> ViewHandle {
223    unsafe {
224        let placement_meta = abi::metadata0(resolve::toolbar_placement_ma());
225        let mut placement =
226            static_getter_indirect(placement.resolved(), placement_meta, 0);
227
228        let item_ctx = Box::leak(Box::new(abi::ClosureCtx::new(
229            emit_tuple,
230            Child::leak(content, Content::Erased),
231        ))) as *mut abi::ClosureCtx;
232
233        let item_meta = abi::resolve_type_by_mangled_name(&TOOLBAR_ITEM_ID_T);
234        assert!(!item_meta.is_null(), "ToolbarItem<String, AnyView> metadata");
235        let mut item = vec![0u8; abi::type_bytes(item_meta)];
236        let id = abi::swift_string(item_id);
237        // ToolbarItem.init(id:placement:showsByDefault:content:)
238        //   x0, x1 = id, x2 = @in placement, x3 = showsByDefault,
239        //   x4 = content fn, x5 = content ctx, x6 = B.Type, x7 = B: View WT,
240        //   x8 = @out. Exactly eight, so nothing spills.
241        core::arch::asm!("blr {f}", f = in(reg) resolve::toolbar_item_id_init(),
242            in("x0") u64::from_le_bytes(id[..8].try_into().expect("id lo")),
243            in("x1") u64::from_le_bytes(id[8..].try_into().expect("id hi")),
244            in("x2") placement.as_mut_ptr(),
245            in("x3") u64::from(shows_by_default),
246            in("x4") abi::swift_closure_thunk as *const c_void,
247            in("x5") item_ctx,
248            in("x6") resolve::anyview_meta(),
249            in("x7") resolve::anyview_wt(),
250            in("x8") item.as_mut_ptr(),
251            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
252            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
253            lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
254            clobber_abi("C"),
255        );
256
257        // `buildBlock` is the same one the plain toolbar uses — it is generic
258        // over `ToolbarContent`, which the customizable item also is.
259        let item_tc_wt = abi::get_view_wt_for_proto(item_meta, resolve::toolbar_content_proto());
260        assert!(!item_tc_wt.is_null(), "ToolbarItem<String, _>: ToolbarContent");
261
262        let tuple_meta = abi::resolve_type_by_mangled_name(&TOOLBAR_TUPLE_ID_T);
263        assert!(!tuple_meta.is_null(), "TupleToolbarContent<ToolbarItem<String, _>> metadata");
264        let mut tuple = vec![0u8; abi::type_bytes(tuple_meta)];
265        core::arch::asm!("blr {f}", f = in(reg) resolve::toolbar_build_block(),
266            in("x0") item.as_mut_ptr(),
267            in("x1") item_meta,
268            in("x2") item_tc_wt,
269            in("x8") tuple.as_mut_ptr(),
270            lateout("x3") _, lateout("x4") _, lateout("x5") _, lateout("x6") _,
271            lateout("x7") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
272            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
273            lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x20") _,
274            lateout("x21") _, clobber_abi("C"),
275        );
276
277        // The conditional conformance: `TupleToolbarContent<T>` is customizable
278        // when `T` is. Asking for it is what proves the item really is.
279        let tuple_custom_wt = abi::get_view_wt_for_proto(
280            tuple_meta,
281            resolve::customizable_toolbar_content_proto(),
282        );
283        assert!(
284            !tuple_custom_wt.is_null(),
285            "TupleToolbarContent<ToolbarItem<String, _>>: CustomizableToolbarContent"
286        );
287
288        let leaked = Box::leak(tuple.into_boxed_slice());
289        let bytes = Box::leak(Box::new(BytesPayload {
290            data: leaked.as_ptr(),
291            len: leaked.len(),
292        }));
293        let ctx = Box::leak(Box::new(abi::ClosureCtx::new(
294            emit_bytes,
295            bytes as *mut BytesPayload as *mut c_void,
296        ))) as *mut abi::ClosureCtx;
297
298        let tid = abi::swift_string(toolbar_id);
299        // toolbar(id:content:) takes the id in x0/x1 and the closure in x2/x3,
300        // and `generic1_modifier_ints` puts Self.Type, T.Type and the two
301        // witnesses in x4–x7 after them. The result type comes from the opaque
302        // return descriptor rather than a mangled name written out here.
303        super::generic1_modifier_ints(
304            resolve::toolbar_id_fn(),
305            resolve::toolbar_id_oret(),
306            view,
307            tuple_meta,
308            tuple_custom_wt,
309            &[
310                u64::from_le_bytes(tid[..8].try_into().expect("toolbar id lo")),
311                u64::from_le_bytes(tid[8..].try_into().expect("toolbar id hi")),
312                abi::swift_closure_thunk as *const c_void as u64,
313                ctx as u64,
314            ],
315            [0.0; 4],
316        )
317    }
318}
319
320#[cfg(not(target_os = "macos"))]
321swift_statics! {
322    /// `SwiftUI.ContentToolbarPlacement` — where a content toolbar attaches.
323    ///
324    /// iOS only, and not by choice: every case is `@available(macOS,
325    /// unavailable)` and SwiftUI ships trap stubs for all three on macOS.
326    /// Measured by calling each getter, not inferred from the interface —
327    /// `examples/traps.rs` is that measurement.
328    pub enum ContentToolbarPlacement {
329        NavigationSplitView = c"$s7SwiftUI23ContentToolbarPlacementV19navigationSplitViewACvgZ",
330        TabViewAccessory = c"$s7SwiftUI23ContentToolbarPlacementV16tabViewAccessoryACvgZ",
331        TabViewSidebar = c"$s7SwiftUI23ContentToolbarPlacementV14tabViewSidebarACvgZ",
332    }
333}
334
335/// `.contentToolbar(for:content:)` — a toolbar attached to a container's
336/// content rather than to the window.
337///
338/// The `Content: View` overload, which is the one that takes an ordinary view
339/// builder; the `ToolbarContent` overload is a different symbol.
340///
341/// iOS only. See [`ContentToolbarPlacement`] — there is no macOS case to pass.
342#[cfg(not(target_os = "macos"))]
343pub fn content_toolbar(
344    view: &ViewHandle,
345    placement: ContentToolbarPlacement,
346    content: &ViewHandle,
347) -> ViewHandle {
348    unsafe {
349        let placement_meta = abi::metadata0(resolve::content_toolbar_placement_ma());
350        assert!(!placement_meta.is_null(), "ContentToolbarPlacement metadata");
351        let mut placement =
352            static_getter_indirect(placement.resolved(), placement_meta, 0);
353
354        // SwiftUI keeps the builder, so the context outlives this call.
355        let ctx = Box::leak(Box::new(abi::ClosureCtx::new(
356            emit_tuple,
357            Child::leak(content, Content::Erased),
358        ))) as *mut abi::ClosureCtx;
359
360        let subst = [
361            view.meta,
362            resolve::anyview_meta(),
363            view.wt,
364            resolve::anyview_wt(),
365        ];
366        let (meta, wt, size) =
367            super::opaque_result_type(resolve::content_toolbar_oret(), &subst);
368        let mut result = vec![0u8; size];
369        let this = view.value_ptr();
370        // x0 = @in placement, x1/x2 = the builder, x3 = Self.Type,
371        // x4 = Content.Type, x5 = Self: View WT, x6 = Content: View WT,
372        // x8 = @out, x20 = self.
373        core::arch::asm!("blr {f}", f = in(reg) resolve::content_toolbar_fn(),
374            in("x0") placement.as_mut_ptr(),
375            in("x1") abi::swift_closure_thunk as *const c_void,
376            in("x2") ctx,
377            in("x3") view.meta,
378            in("x4") resolve::anyview_meta(),
379            in("x5") view.wt,
380            in("x6") resolve::anyview_wt(),
381            in("x8") result.as_mut_ptr(),
382            in("x20") this,
383            lateout("x7") _,
384            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
385            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
386            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
387        );
388        wrap_owned(result, meta, wt)
389    }
390}