Skip to main content

swiftui_native/
scene.rs

1//! Scenes and commands — what an app is, above its views.
2//!
3//! [`crate::app::run`] builds one `WindowGroup` and stops there. A real macOS
4//! app is several scenes: the window group, the preferences window `Settings`
5//! puts behind ⌘, — a menu-bar item, a second window — and a menu bar described
6//! by `Commands`. This module builds each of them, and the tuples that hold
7//! several at once, so [`crate::app::run_scene`] can be given a whole app rather
8//! than one window.
9//!
10//! ```no_run
11//! use swiftui_native::{app, scene, views};
12//!
13//! app::run_scene(|| {
14//!     scene::commands(
15//!         scene::group(vec![
16//!             scene::window_group(|| views::text("main")),
17//!             scene::settings(|| views::text("preferences")),
18//!             scene::menu_bar_extra("Status", "star", || views::text("menu")),
19//!         ]),
20//!         vec![scene::command_menu("Extras", || views::button("Do it", || {}))],
21//!     )
22//! });
23//! ```
24//!
25//! # Why the content closures are borrowed
26//!
27//! Every initialiser here takes its content as `@noescape` — `xyXE` in the
28//! mangling, not `yc` — so it calls the closure before returning and keeps the
29//! *value*, not the closure. A borrowed [`crate::abi::ClosureCtx`] on the stack
30//! is therefore correct, and leaking one (as a `sheet`'s content must be) would
31//! leak once per read of `App.body`, which SwiftUI does repeatedly.
32//!
33//! # What is synthesised and what is SwiftUI's
34//!
35//! Nothing here is a Swift wrapper: each scene is SwiftUI's own type,
36//! instantiated by calling its own initialiser with the arguments in the
37//! registers its ABI names. `_TupleScene` and `TupleCommandContent` are what
38//! `SceneBuilder` and `CommandsBuilder` build — those builders are
39//! `@_alwaysEmitIntoClient` and export no symbol, so the binding calls what
40//! their bodies call.
41
42use crate::abi;
43// Only `menu_bar_extra` names a type by hand, and it is macOS-only.
44#[cfg(target_os = "macos")]
45use crate::mangle::{self, swift_type};
46use crate::resolve;
47use crate::view::IntoView;
48use crate::views::{self, ViewHandle};
49use core::ffi::c_void;
50
51/// A Swift value with its type and one protocol conformance.
52///
53/// The same shape as [`ViewHandle`], and for the same reason: the type is only
54/// known at run time, so the bytes travel with the metadata that describes them
55/// and the witness table that says what they are. Both [`SceneHandle`] and
56/// [`CommandsHandle`] are this — distinct types at the Rust level so that
57/// `commands(_:)` cannot be handed a scene, which is the one thing the
58/// conformance itself would not catch.
59struct Value {
60    bytes: Vec<u8>,
61    meta: *const c_void,
62    wt: *const c_void,
63}
64
65impl Value {
66    /// Take ownership of a `+1` value the caller has just built.
67    ///
68    /// # Safety
69    /// `bytes` must hold an initialised value of `meta`'s type whose single
70    /// reference is being handed over.
71    unsafe fn wrap(bytes: Vec<u8>, meta: *const c_void, wt: *const c_void) -> Self {
72        debug_assert!(!meta.is_null() && !wt.is_null(), "a value needs a type and a witness");
73        Self { bytes, meta, wt }
74    }
75
76    fn ptr(&self) -> *const c_void {
77        self.bytes.as_ptr() as *const c_void
78    }
79
80    /// Move the value into `out` and give up ownership of it.
81    ///
82    /// For `App.body`, whose result slot SwiftUI supplies: copying and then
83    /// letting `Drop` run would destroy the reference just handed over, and
84    /// SwiftUI would read freed storage on the next frame.
85    ///
86    /// # Safety
87    /// `out` must have room for the type's stride.
88    unsafe fn move_into(self, out: *mut u8) {
89        let me = core::mem::ManuallyDrop::new(self);
90        unsafe { core::ptr::copy_nonoverlapping(me.bytes.as_ptr(), out, me.bytes.len()) };
91    }
92}
93
94impl Drop for Value {
95    fn drop(&mut self) {
96        unsafe {
97            let vwt = &*swift_runtime_sys::SwiftABI::get_value_witness_table(self.meta);
98            if vwt.size > 0 {
99                (vwt.destroy)(self.bytes.as_mut_ptr() as *mut c_void, self.meta);
100            }
101        }
102    }
103}
104
105/// A value conforming to SwiftUI's `Scene` — a window, a settings pane, a
106/// menu-bar item, or a tuple of them.
107pub struct SceneHandle(Value);
108
109/// A value conforming to SwiftUI's `Commands` — one menu, or a tuple of them.
110pub struct CommandsHandle(Value);
111
112impl SceneHandle {
113    /// The Swift type of the scene, as the runtime spells it.
114    pub fn type_name(&self) -> String {
115        crate::diag::type_name(self.0.meta)
116    }
117
118    /// Move the scene into `out`, giving up ownership.
119    ///
120    /// # Safety
121    /// `out` must have room for the type's stride.
122    pub(crate) unsafe fn move_into(self, out: *mut u8) {
123        unsafe { self.0.move_into(out) }
124    }
125
126    pub(crate) fn meta(&self) -> *const c_void {
127        self.0.meta
128    }
129
130    pub(crate) fn wt(&self) -> *const c_void {
131        self.0.wt
132    }
133}
134
135impl CommandsHandle {
136    /// The Swift type of the commands, as the runtime spells it.
137    pub fn type_name(&self) -> String {
138        crate::diag::type_name(self.0.meta)
139    }
140}
141
142// ── The content closure ──
143
144/// What a scene's or menu's `@ViewBuilder content:` closure needs.
145struct Content<'a> {
146    builder: &'a dyn Fn() -> ViewHandle,
147}
148
149/// Emit the builder's view, erased into an `AnyView`.
150///
151/// A Rust closure's result type is decided at run time, so the content type is
152/// `AnyView` — the same trade every closure-valued binding in this crate makes.
153///
154/// # Safety
155/// Reached through [`abi::swift_closure_thunk`]; `payload` must be a [`Content`].
156unsafe extern "C" fn emit_content(out: *mut u8, payload: *mut c_void) {
157    let p = unsafe { &*(payload as *const Content<'_>) };
158    let view = (p.builder)();
159    unsafe { *(out as *mut u64) = view.into_anyview_word() };
160}
161
162/// A type's conformance to `proto`, or a panic naming what failed.
163///
164/// `name` is only for the message: a protocol descriptor carries its name, but
165/// not anywhere a caller can read it without walking the descriptor, and the
166/// caller already knows which one it asked for.
167///
168/// # Safety
169/// `meta` must be valid metadata and `proto` a protocol descriptor.
170unsafe fn conformance(
171    meta: *const c_void,
172    proto: *const c_void,
173    name: &str,
174) -> *const c_void {
175    let wt = unsafe { abi::get_view_wt_for_proto(meta, proto) };
176    assert!(!wt.is_null(), "{} !: {name}", crate::diag::type_name(meta));
177    wt
178}
179
180/// `X<AnyView>` for a type generic over one `Content: View`.
181///
182/// # Safety
183/// `ma` must be such a type's metadata accessor.
184unsafe fn over_anyview(ma: *const c_void) -> *const c_void {
185    let meta = unsafe {
186        abi::generic_metadata2(ma, resolve::anyview_meta(), resolve::anyview_wt())
187    };
188    assert!(!meta.is_null(), "metadata instantiation failed");
189    meta
190}
191
192/// An initialiser whose whole argument list is one `@ViewBuilder content:`.
193///
194/// `WindowGroup.init(content:)`, `Settings.init(content:)` and
195/// `CommandMenu`'s content have this shape — `(sret, closure fn, closure ctx,
196/// Content.Type, Content: View)` — so one body serves all of them.
197///
198/// # Safety
199/// `init` must be such an initialiser and `meta` the result's metadata.
200unsafe fn build_from_content(
201    init: *const c_void,
202    meta: *const c_void,
203    builder: &dyn Fn() -> ViewHandle,
204) -> Vec<u8> {
205    unsafe {
206        let payload = Content { builder };
207        let mut ctx = abi::ClosureCtx::new(
208            emit_content,
209            &payload as *const Content<'_> as *mut c_void,
210        );
211        let mut bytes = vec![0u8; abi::type_bytes(meta)];
212        core::arch::asm!("blr {f}", f = in(reg) init,
213            in("x0") abi::swift_closure_thunk as *const c_void,
214            in("x1") &mut ctx as *mut abi::ClosureCtx,
215            in("x2") resolve::anyview_meta(),
216            in("x3") resolve::anyview_wt(),
217            in("x8") bytes.as_mut_ptr(),
218            lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
219            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
220            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
221            lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
222            clobber_abi("C"),
223        );
224        bytes
225    }
226}
227
228/// An initialiser taking four words of label, two of string, then the content.
229///
230/// `Window` and `MenuBarExtra` are the only two, and both are macOS-only.
231///
232/// `Window(_:id:content:)` and `MenuBarExtra(_:systemImage:content:)` both look
233/// like this. Eight integer registers is all there are, so the two generic
234/// arguments go on the stack.
235///
236/// # Safety
237/// `init` must be such an initialiser and `meta` the result's metadata.
238#[cfg(target_os = "macos")]
239unsafe fn build_from_key_string_content(
240    init: *const c_void,
241    meta: *const c_void,
242    key: &str,
243    string: &str,
244    builder: &dyn Fn() -> ViewHandle,
245) -> Vec<u8> {
246    unsafe {
247        let (k0, k1, k2, k3) = views::lsk_words(key);
248        let s = abi::swift_string(string);
249        let s0 = u64::from_le_bytes(s[..8].try_into().expect("string lo"));
250        let s1 = u64::from_le_bytes(s[8..].try_into().expect("string hi"));
251        let payload = Content { builder };
252        let mut ctx = abi::ClosureCtx::new(
253            emit_content,
254            &payload as *const Content<'_> as *mut c_void,
255        );
256        let mut bytes = vec![0u8; abi::type_bytes(meta)];
257        // x0-x3 = LocalizedStringKey, x4/x5 = the String, x6/x7 = the closure,
258        // [sp] = Content.Type, [sp+8] = Content: View.
259        core::arch::asm!(
260            "sub sp, sp, #16",
261            "stp {cm}, {cw}, [sp]",
262            "blr {f}",
263            "add sp, sp, #16",
264            f = in(reg) init,
265            cm = in(reg) resolve::anyview_meta(),
266            cw = in(reg) resolve::anyview_wt(),
267            in("x0") k0, in("x1") k1, in("x2") k2, in("x3") k3,
268            in("x4") s0, in("x5") s1,
269            in("x6") abi::swift_closure_thunk as *const c_void,
270            in("x7") &mut ctx as *mut abi::ClosureCtx,
271            in("x8") bytes.as_mut_ptr(),
272            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
273            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
274            lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
275            clobber_abi("C"),
276        );
277        bytes
278    }
279}
280
281// ── Scenes ──
282
283/// `WindowGroup { content }` — the app's main window, and as many more as the
284/// user opens.
285pub fn window_group<V: IntoView>(content: impl Fn() -> V) -> SceneHandle {
286    let content = move || content().into_view().into_handle();
287    unsafe {
288        let meta = over_anyview(resolve::windowgroup_ma());
289        let bytes = build_from_content(resolve::windowgroup_content_init(), meta, &content);
290        SceneHandle(Value::wrap(bytes, meta, conformance(meta, resolve::scene_proto(), "Scene")))
291    }
292}
293
294/// `Settings { content }` — the window behind ⌘, on macOS.
295#[cfg(target_os = "macos")]
296pub fn settings<V: IntoView>(content: impl Fn() -> V) -> SceneHandle {
297    let content = move || content().into_view().into_handle();
298    unsafe {
299        let meta = over_anyview(resolve::settings_ma());
300        let bytes = build_from_content(resolve::settings_content_init(), meta, &content);
301        SceneHandle(Value::wrap(bytes, meta, conformance(meta, resolve::scene_proto(), "Scene")))
302    }
303}
304
305/// `Window(title, id: id) { content }` — a single window, not a group.
306///
307/// Unlike a `WindowGroup` the user cannot open a second one; `id` is what
308/// `openWindow` names it by.
309#[cfg(target_os = "macos")]
310pub fn window<V: IntoView>(title: &str, id: &str, content: impl Fn() -> V) -> SceneHandle {
311    let content = move || content().into_view().into_handle();
312    unsafe {
313        let meta = over_anyview(resolve::window_ma());
314        let bytes =
315            build_from_key_string_content(resolve::window_init(), meta, title, id, &content);
316        SceneHandle(Value::wrap(bytes, meta, conformance(meta, resolve::scene_proto(), "Scene")))
317    }
318}
319
320/// `MenuBarExtra(title, systemImage: image) { content }` — an item in the
321/// system menu bar, with a menu of its own.
322///
323/// The `Label == Label<Text, Image>` overload, the one that takes a title and an
324/// SF Symbol. Its `Label` type argument is concrete, so the metadata is resolved
325/// by name rather than through the accessor — the accessor would want
326/// `Label<Text, Image>`'s own metadata and both of its `View` conformances first,
327/// which is three more lookups to reach the same answer.
328#[cfg(target_os = "macos")]
329pub fn menu_bar_extra<V: IntoView>(
330    title: &str,
331    system_image: &str,
332    content: impl Fn() -> V,
333) -> SceneHandle {
334    let content = move || content().into_view().into_handle();
335    unsafe {
336        let label = mangle::bound(
337            &swift_type!(SwiftUI.Label),
338            &[&swift_type!(SwiftUI.Text), &swift_type!(SwiftUI.Image)],
339        );
340        let named = mangle::bound(
341            &swift_type!(SwiftUI.MenuBarExtra),
342            &[&label, &swift_type!(SwiftUI.AnyView)],
343        );
344        let meta = abi::resolve_type_by_mangled_name(named.as_bytes());
345        assert!(!meta.is_null(), "{}", mangle::demangle(&named));
346        let bytes = build_from_key_string_content(
347            resolve::menu_bar_extra_init(),
348            meta,
349            title,
350            system_image,
351            &content,
352        );
353        SceneHandle(Value::wrap(bytes, meta, conformance(meta, resolve::scene_proto(), "Scene")))
354    }
355}
356
357/// Several scenes as one — `_TupleScene<(A, B, …)>`.
358///
359/// What `SceneBuilder`'s variadic `buildBlock` returns, so this is what writing
360/// four scenes one after another inside `var body: some Scene` reduces to. A
361/// single scene is returned unchanged, as the one-element `buildBlock` does.
362pub fn group(scenes: Vec<SceneHandle>) -> SceneHandle {
363    assert!(!scenes.is_empty(), "a scene group needs at least one scene");
364    if scenes.len() == 1 {
365        return scenes.into_iter().next().expect("one scene");
366    }
367    unsafe {
368        SceneHandle(tuple_of(
369            scenes.into_iter().map(|s| s.0).collect(),
370            resolve::tuple_scene_ma(),
371            resolve::tuple_scene_init(),
372            resolve::scene_proto(),
373            "Scene",
374        ))
375    }
376}
377
378/// Build `Wrapper<(A, B, …)>` from a list of values of mixed types.
379///
380/// Shared by `_TupleScene` and `TupleCommandContent`, which have the same shape:
381/// one unconstrained generic parameter, an initialiser taking the tuple `@in`
382/// plus `T.Type`, and an unconditional conformance.
383///
384/// The elements' *own* types go into the tuple, not an erased stand-in: a
385/// `_TupleScene<(WindowGroup<AnyView>, Settings<AnyView>)>` is what Swift
386/// builds, and the runtime finds each element's conformance by reflecting over
387/// that tuple — an erased tuple would render and be the wrong type.
388///
389/// # Safety
390/// `ma` and `init` must be the wrapper's accessor and initialiser, and `proto`
391/// the protocol the wrapper is being used for.
392unsafe fn tuple_of(
393    parts: Vec<Value>,
394    ma: *const c_void,
395    init: *const c_void,
396    proto: *const c_void,
397    name: &str,
398) -> Value {
399    unsafe {
400        let elements: Vec<*const c_void> = parts.iter().map(|p| p.meta).collect();
401        let tuple_meta = abi::tuple_metadata(&elements);
402        assert!(!tuple_meta.is_null(), "tuple metadata instantiation failed");
403        // The runtime chose the layout; asking it is the only way to know it.
404        let offsets = abi::tuple_element_offsets(tuple_meta, parts.len());
405        let mut tuple = vec![0u8; abi::type_bytes(tuple_meta)];
406        for (part, &offset) in parts.iter().zip(&offsets) {
407            let vwt = &*swift_runtime_sys::SwiftABI::get_value_witness_table(part.meta);
408            if vwt.size > 0 {
409                (vwt.initialize_with_copy)(
410                    tuple.as_mut_ptr().add(offset) as *mut c_void,
411                    part.ptr() as *mut c_void,
412                    part.meta,
413                );
414            }
415        }
416
417        let meta = abi::generic_metadata1(ma, tuple_meta);
418        assert!(!meta.is_null(), "tuple wrapper metadata instantiation failed");
419        let mut bytes = vec![0u8; abi::type_bytes(meta)];
420        // x0 = @in the tuple, x1 = T.Type, x8 = @out.
421        core::arch::asm!("blr {f}", f = in(reg) init,
422            in("x0") tuple.as_mut_ptr(),
423            in("x1") tuple_meta,
424            in("x8") bytes.as_mut_ptr(),
425            lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
426            lateout("x6") _, lateout("x7") _,
427            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
428            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
429            lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
430            clobber_abi("C"),
431        );
432        // The initialiser consumed the tuple, which held a copy of each part;
433        // the parts themselves are still ours, and dropping them destroys those.
434        drop(parts);
435        Value::wrap(bytes, meta, conformance(meta, proto, name))
436    }
437}
438
439// ── Commands — the menu bar ──
440
441/// `CommandMenu(title) { content }` — a whole new menu in the menu bar.
442pub fn command_menu<V: IntoView>(title: &str, content: impl Fn() -> V) -> CommandsHandle {
443    let content = move || content().into_view().into_handle();
444    unsafe {
445        let meta = over_anyview(resolve::command_menu_ma());
446        let builder: &dyn Fn() -> ViewHandle = &content;
447        let (k0, k1, k2, k3) = views::lsk_words(title);
448        let payload = Content { builder };
449        let mut ctx = abi::ClosureCtx::new(
450            emit_content,
451            &payload as *const Content<'_> as *mut c_void,
452        );
453        let mut bytes = vec![0u8; abi::type_bytes(meta)];
454        // x0-x3 = LocalizedStringKey, x4/x5 = the closure,
455        // x6 = Content.Type, x7 = Content: View, x8 = @out.
456        core::arch::asm!("blr {f}", f = in(reg) resolve::command_menu_init(),
457            in("x0") k0, in("x1") k1, in("x2") k2, in("x3") k3,
458            in("x4") abi::swift_closure_thunk as *const c_void,
459            in("x5") &mut ctx as *mut abi::ClosureCtx,
460            in("x6") resolve::anyview_meta(),
461            in("x7") resolve::anyview_wt(),
462            in("x8") bytes.as_mut_ptr(),
463            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
464            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
465            lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
466            clobber_abi("C"),
467        );
468        CommandsHandle(Value::wrap(
469            bytes,
470            meta,
471            conformance(meta, resolve::commands_proto(), "Commands"),
472        ))
473    }
474}
475
476swift_statics! {
477    /// Where in the standard menu bar a [`command_group`] goes.
478    ///
479    /// The twenty-one placements SwiftUI exports, each a static property of
480    /// `CommandGroupPlacement` read through its own getter rather than a number
481    /// written down — the type is resilient and its layout is not API.
482    pub enum CommandPlacement {
483        AppInfo = c"$s7SwiftUI21CommandGroupPlacementV7appInfoACvgZ",
484        AppSettings = c"$s7SwiftUI21CommandGroupPlacementV11appSettingsACvgZ",
485        AppShortcuts = c"$s7SwiftUI21CommandGroupPlacementV12appShortcutsACvgZ",
486        AppSpecificSettings = c"$s7SwiftUI21CommandGroupPlacementV11appSettingsACvgZ",
487        AppTermination = c"$s7SwiftUI21CommandGroupPlacementV14appTerminationACvgZ",
488        AppVisibility = c"$s7SwiftUI21CommandGroupPlacementV13appVisibilityACvgZ",
489        Help = c"$s7SwiftUI21CommandGroupPlacementV4helpACvgZ",
490        ImportExport = c"$s7SwiftUI21CommandGroupPlacementV12importExportACvgZ",
491        NewItem = c"$s7SwiftUI21CommandGroupPlacementV7newItemACvgZ",
492        Pasteboard = c"$s7SwiftUI21CommandGroupPlacementV10pasteboardACvgZ",
493        PrintItem = c"$s7SwiftUI21CommandGroupPlacementV9printItemACvgZ",
494        SaveItem = c"$s7SwiftUI21CommandGroupPlacementV8saveItemACvgZ",
495        Sidebar = c"$s7SwiftUI21CommandGroupPlacementV7sidebarACvgZ",
496        SingleWindowList = c"$s7SwiftUI21CommandGroupPlacementV16singleWindowListACvgZ",
497        SystemServices = c"$s7SwiftUI21CommandGroupPlacementV14systemServicesACvgZ",
498        TextEditing = c"$s7SwiftUI21CommandGroupPlacementV11textEditingACvgZ",
499        TextFormatting = c"$s7SwiftUI21CommandGroupPlacementV14textFormattingACvgZ",
500        Toolbar = c"$s7SwiftUI21CommandGroupPlacementV7toolbarACvgZ",
501        UndoRedo = c"$s7SwiftUI21CommandGroupPlacementV8undoRedoACvgZ",
502        WindowArrangement = c"$s7SwiftUI21CommandGroupPlacementV17windowArrangementACvgZ",
503        WindowList = c"$s7SwiftUI21CommandGroupPlacementV10windowListACvgZ",
504        WindowSize = c"$s7SwiftUI21CommandGroupPlacementV10windowSizeACvgZ",
505    }
506}
507
508/// Where a group sits relative to the placement it names.
509#[derive(Clone, Copy, Debug, PartialEq, Eq)]
510pub enum Relative {
511    /// Take the standard group's place entirely.
512    Replacing,
513    /// Add after it.
514    After,
515    /// Add before it.
516    Before,
517}
518
519/// `CommandGroup(replacing:/after:/before: placement) { content }` — items
520/// added to, or in place of, one of the standard menu groups.
521pub fn command_group<V: IntoView>(
522    placement: CommandPlacement,
523    where_: Relative,
524    content: impl Fn() -> V,
525) -> CommandsHandle {
526    let content = move || content().into_view().into_handle();
527    unsafe {
528        let meta = over_anyview(resolve::command_group_ma());
529        let placement_meta = abi::metadata0(resolve::command_group_placement_ma());
530        assert!(!placement_meta.is_null(), "CommandGroupPlacement metadata");
531        // Resilient, so it travels by address and its stride is asked for.
532        let mut place =
533            views::static_getter_indirect(placement.resolved(), placement_meta, 0);
534
535        let init = match where_ {
536            Relative::Replacing => resolve::command_group_replacing_init(),
537            Relative::After => resolve::command_group_after_init(),
538            Relative::Before => resolve::command_group_before_init(),
539        };
540        let builder: &dyn Fn() -> ViewHandle = &content;
541        let payload = Content { builder };
542        let mut ctx = abi::ClosureCtx::new(
543            emit_content,
544            &payload as *const Content<'_> as *mut c_void,
545        );
546        let mut bytes = vec![0u8; abi::type_bytes(meta)];
547        // x0 = @in placement, x1/x2 = the closure,
548        // x3 = Content.Type, x4 = Content: View, x8 = @out.
549        core::arch::asm!("blr {f}", f = in(reg) init,
550            in("x0") place.as_mut_ptr(),
551            in("x1") abi::swift_closure_thunk as *const c_void,
552            in("x2") &mut ctx as *mut abi::ClosureCtx,
553            in("x3") resolve::anyview_meta(),
554            in("x4") resolve::anyview_wt(),
555            in("x8") bytes.as_mut_ptr(),
556            lateout("x5") _, lateout("x6") _, lateout("x7") _,
557            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
558            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
559            lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
560            clobber_abi("C"),
561        );
562        CommandsHandle(Value::wrap(
563            bytes,
564            meta,
565            conformance(meta, resolve::commands_proto(), "Commands"),
566        ))
567    }
568}
569
570/// `.commands { … }` on a scene — the app's menu bar.
571///
572/// The menus are combined into a `TupleCommandContent`, which is what
573/// `CommandsBuilder.buildBlock` builds. An empty list is rejected rather than
574/// silently producing a scene with no menus: `commands` with nothing in it is a
575/// mistake, not a shorthand.
576pub fn commands(scene: SceneHandle, menus: Vec<CommandsHandle>) -> SceneHandle {
577    assert!(!menus.is_empty(), "`commands` needs at least one menu");
578    unsafe {
579        let content = if menus.len() == 1 {
580            menus.into_iter().next().expect("one menu").0
581        } else {
582            tuple_of(
583                menus.into_iter().map(|m| m.0).collect(),
584                resolve::tuple_commands_ma(),
585                resolve::tuple_commands_init(),
586                resolve::commands_proto(),
587                "Commands",
588            )
589        };
590
591        // The content closure here returns `Commands`, not a view, so it cannot
592        // go through `emit_content`: what it emits is the tuple built above.
593        let payload = EmitValue { value: content.ptr(), meta: content.meta };
594        let mut ctx = abi::ClosureCtx::new(
595            emit_value,
596            &payload as *const EmitValue as *mut c_void,
597        );
598
599        let subst = [scene.meta(), content.meta, scene.wt(), content.wt];
600        let (meta, wt, size) =
601            views::opaque_result_type(resolve::scene_commands_oret(), &subst);
602        let mut bytes = vec![0u8; size];
603        let this = scene.0.ptr();
604        // x0/x1 = the content closure, x2 = Self.Type, x3 = C.Type,
605        // x4 = Self: Scene, x5 = C: Commands, x8 = @out, x20 = self.
606        core::arch::asm!("blr {f}", f = in(reg) resolve::scene_commands_fn(),
607            in("x0") abi::swift_closure_thunk as *const c_void,
608            in("x1") &mut ctx as *mut abi::ClosureCtx,
609            in("x2") scene.meta(),
610            in("x3") content.meta,
611            in("x4") scene.wt(),
612            in("x5") content.wt,
613            in("x8") bytes.as_mut_ptr(),
614            in("x20") this,
615            lateout("x6") _, lateout("x7") _,
616            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
617            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
618            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
619        );
620        SceneHandle(Value::wrap(bytes, meta, wt))
621    }
622}
623
624/// What [`emit_value`] copies.
625#[repr(C)]
626struct EmitValue {
627    value: *const c_void,
628    meta: *const c_void,
629}
630
631/// A closure body that emits an already-built value of any type.
632///
633/// `.commands(content:)`'s closure returns `some Commands`, so the result is a
634/// value of a type only the payload knows — copied through its own witness
635/// rather than written as a word, the way an `AnyView` can be.
636///
637/// # Safety
638/// Reached through [`abi::swift_closure_thunk`]; `payload` must be an
639/// [`EmitValue`] whose `value` is live.
640unsafe extern "C" fn emit_value(out: *mut u8, payload: *mut c_void) {
641    let p = unsafe { &*(payload as *const EmitValue) };
642    unsafe {
643        let vwt = &*swift_runtime_sys::SwiftABI::get_value_witness_table(p.meta);
644        if vwt.size > 0 {
645            (vwt.initialize_with_copy)(out as *mut c_void, p.value as *mut c_void, p.meta);
646        }
647    }
648}