swiftui-native 0.0.1

SwiftUI from pure Rust — no Swift bridge, no shim, only swift-runtime calls
Documentation
//! Toolbar.

use super::*;
use crate::abi;
use crate::resolve;
use core::ffi::c_void;
use crate::mangle;

// ── Toolbar ──

mangle::result_types! {
    /// `ToolbarItem<(), AnyView>`
    /// `ToolbarItem<(), AnyView>`
    pub(crate) TOOLBAR_ITEM_T = ty("ToolbarItem").of(vec![Type::Void, ty("AnyView")]);
    /// `TupleToolbarContent<ToolbarItem<(), AnyView>>`
    /// `TupleToolbarContent<ToolbarItem<(), AnyView>>`
    pub(crate) TOOLBAR_TUPLE_T = ty("TupleToolbarContent").of(vec![ty("ToolbarItem").of(vec![Type::Void, ty("AnyView")])]);
    /// `ToolbarItem<String, AnyView>` — the customizable item.
    /// `ToolbarItem<String, AnyView>`
    pub(crate) TOOLBAR_ITEM_ID_T = ty("ToolbarItem").of(vec![Type::Verbatim("SS".into()), ty("AnyView")]);
    /// `TupleToolbarContent<ToolbarItem<String, AnyView>>`
    /// `TupleToolbarContent<ToolbarItem<String, AnyView>>`
    pub(crate) TOOLBAR_TUPLE_ID_T = ty("TupleToolbarContent").of(vec![ty("ToolbarItem").of(vec![Type::Verbatim("SS".into()), ty("AnyView")])]);
}
mangle::modifier_types! {
    /// `.toolbar(content:)`
    /// `ModifiedContent<AnyView, ToolbarModifier<(), TupleToolbarContent<ToolbarItem<(), AnyView>>>>`
    pub(crate) TOOLBAR_T = ty("ToolbarModifier").of(vec![Type::Void, ty("TupleToolbarContent").of(vec![ty("ToolbarItem").of(vec![Type::Void, ty("AnyView")])])]);
}



swift_statics! {
    /// `SwiftUI.ToolbarItemPlacement` — where a toolbar item goes.
    ///
    /// The cases are the type's documented static properties, on the platforms
    /// this crate targets. The iOS-only spellings — `navigationBarLeading`,
    /// `bottomBar`, `topBarTrailing` and the rest — are
    /// `@available(macOS, unavailable)` and are left out rather than bound to
    /// trap; `examples/statics` is what says which those are.
    pub enum ToolbarItemPlacement {
        Automatic = c"$s7SwiftUI20ToolbarItemPlacementV9automaticACvgZ",
        CancellationAction = c"$s7SwiftUI20ToolbarItemPlacementV18cancellationActionACvgZ",
        ConfirmationAction = c"$s7SwiftUI20ToolbarItemPlacementV18confirmationActionACvgZ",
        DestructiveAction = c"$s7SwiftUI20ToolbarItemPlacementV17destructiveActionACvgZ",
        Keyboard = c"$s7SwiftUI20ToolbarItemPlacementV8keyboardACvgZ",
        Navigation = c"$s7SwiftUI20ToolbarItemPlacementV10navigationACvgZ",
        PrimaryAction = c"$s7SwiftUI20ToolbarItemPlacementV13primaryActionACvgZ",
        Principal = c"$s7SwiftUI20ToolbarItemPlacementV9principalACvgZ",
        SecondaryAction = c"$s7SwiftUI20ToolbarItemPlacementV15secondaryActionACvgZ",
        Status = c"$s7SwiftUI20ToolbarItemPlacementV6statusACvgZ",
    }
}

/// A closure body that writes back a value built in advance.
#[repr(C)]
pub(crate) struct BytesPayload {
    pub(crate) data: *const u8,
    pub(crate) len: usize,
}

pub(crate) unsafe extern "C" fn emit_bytes(out: *mut u8, payload: *mut c_void) {
    let p = unsafe { &*(payload as *const BytesPayload) };
    unsafe { core::ptr::copy_nonoverlapping(p.data, out, p.len) };
}

/// Apply `.toolbar { ToolbarItem { content } }`.
///
/// The toolbar takes a `ToolbarContentBuilder` closure, not a `ViewBuilder` one,
/// and unlike `ViewBuilder` it does *not* collapse a single element — one item
/// still becomes a `TupleToolbarContent`. So this is three chained calls: build
/// the item, wrap it through `buildBlock`, then hand the result to `toolbar`.
pub fn toolbar(view: &ViewHandle, content: &ViewHandle) -> ViewHandle {
    toolbar_placed(view, ToolbarItemPlacement::Automatic, content)
}

/// Apply `.toolbar { ToolbarItem(placement:) { content } }`.
///
/// `toolbar` above is this with `.automatic`, which is what the placement
/// argument defaults to in Swift.
pub fn toolbar_placed(
    view: &ViewHandle,
    placement: ToolbarItemPlacement,
    content: &ViewHandle,
) -> ViewHandle {
    unsafe {
        // `ToolbarItemPlacement` is 41 bytes, so indirect throughout.
        let placement_meta = abi::metadata0(resolve::toolbar_placement_ma());
        let mut placement =
            static_getter_indirect(placement.resolved(), placement_meta, 0);

        // The item's content closure. It is retained by the toolbar, so leak it.
        let item_ctx = Box::leak(Box::new(abi::ClosureCtx::new(
            emit_tuple,
            Child::leak(content, Content::Erased),
        ))) as *mut abi::ClosureCtx;

        let (item_meta, _, item_size) = {
            let m = abi::resolve_type_by_mangled_name(&TOOLBAR_ITEM_T);
            assert!(!m.is_null(), "ToolbarItem<(), AnyView> metadata");
            (m, (), abi::type_bytes(m))
        };
        let mut item = vec![0u8; item_size];
        // ToolbarItem.init(placement:content:)
        //   x0 = @in placement, x1 = content fn, x2 = content ctx,
        //   x3 = B.Type, x4 = B: View WT, x8 = @out
        core::arch::asm!("blr {f}", f = in(reg) resolve::toolbar_item_init(),
            in("x0") placement.as_mut_ptr(),
            in("x1") abi::swift_closure_thunk as *const c_void,
            in("x2") item_ctx,
            in("x3") resolve::anyview_meta(),
            in("x4") resolve::anyview_wt(),
            in("x8") item.as_mut_ptr(),
            lateout("x5") _, lateout("x6") _, lateout("x7") _,
            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
            lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
            clobber_abi("C"),
        );

        let item_tc_wt =
            abi::get_view_wt_for_proto(item_meta, resolve::toolbar_content_proto());
        assert!(!item_tc_wt.is_null(), "ToolbarItem: ToolbarContent");

        // ToolbarContentBuilder.buildBlock(item)
        //   x0 = @in C, x1 = C.Type, x2 = C: ToolbarContent WT, x8 = @out
        let tuple_meta = abi::resolve_type_by_mangled_name(&TOOLBAR_TUPLE_T);
        assert!(!tuple_meta.is_null(), "TupleToolbarContent metadata");
        let mut tuple = vec![0u8; abi::type_bytes(tuple_meta)];
        core::arch::asm!("blr {f}", f = in(reg) resolve::toolbar_build_block(),
            in("x0") item.as_mut_ptr(),
            in("x1") item_meta,
            in("x2") item_tc_wt,
            in("x8") tuple.as_mut_ptr(),
            lateout("x3") _, lateout("x4") _, lateout("x5") _, lateout("x6") _,
            lateout("x7") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
            lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x20") _,
            lateout("x21") _, clobber_abi("C"),
        );

        let tuple_tc_wt =
            abi::get_view_wt_for_proto(tuple_meta, resolve::toolbar_content_proto());
        assert!(!tuple_tc_wt.is_null(), "TupleToolbarContent: ToolbarContent");

        // The toolbar's own content closure just hands back the tuple we built.
        let leaked = Box::leak(tuple.into_boxed_slice());
        let bytes = Box::leak(Box::new(BytesPayload {
            data: leaked.as_ptr(),
            len: leaked.len(),
        }));
        let ctx = Box::leak(Box::new(abi::ClosureCtx::new(
            emit_bytes,
            bytes as *mut BytesPayload as *mut c_void,
        ))) as *mut abi::ClosureCtx;

        let (_, _, size) = TOOLBAR_T.applied_to(view.meta);
        let mut result = vec![0u8; size];
        let this = view.value_ptr();
        // toolbar<T: ToolbarContent>(content: () -> T)
        //   x0 = closure fn, x1 = closure ctx, x2 = Self.Type, x3 = T.Type,
        //   x4 = Self: View WT, x5 = T: ToolbarContent WT, x8 = @out, x20 = self
        core::arch::asm!("blr {f}", f = in(reg) resolve::toolbar_fn(),
            in("x0") abi::swift_closure_thunk as *const c_void,
            in("x1") ctx,
            in("x2") view.meta,
            in("x3") tuple_meta,
            in("x4") content.wt,
            in("x5") tuple_tc_wt,
            in("x8") result.as_mut_ptr(),
            in("x20") this,
            lateout("x6") _, lateout("x7") _,
            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
        );
        wrap_modified(result, &TOOLBAR_T, view)
    }
}

/// Apply `.toolbar(id:) { ToolbarItem(id:) { content } }` — a toolbar the user
/// can rearrange, whose arrangement the system persists under `toolbar_id`.
///
/// Customizability is a property of the item's *type*, not a flag on the call:
/// the conformance is `extension ToolbarItem: CustomizableToolbarContent where
/// ID == String`, so an item built by [`toolbar`]'s `init(placement:content:)`
/// has `ID == ()` and does not conform. This builds `ToolbarItem<String,
/// AnyView>` through the id-taking initialiser instead, and every witness below
/// is looked up against `CustomizableToolbarContent` rather than
/// `ToolbarContent` — `TupleToolbarContent` conforms conditionally, on its
/// element conforming, which is why the item has to be the customizable one
/// first.
///
/// `shows_by_default` is whether the item appears before the user has
/// customized anything; one set to `false` exists only in the customization
/// palette until it is dragged in.
pub fn toolbar_customizable(
    view: &ViewHandle,
    toolbar_id: &str,
    item_id: &str,
    shows_by_default: bool,
    content: &ViewHandle,
) -> ViewHandle {
    toolbar_customizable_placed(
        view,
        toolbar_id,
        item_id,
        ToolbarItemPlacement::Automatic,
        shows_by_default,
        content,
    )
}

/// The same, with the item placed somewhere other than `.automatic`.
pub fn toolbar_customizable_placed(
    view: &ViewHandle,
    toolbar_id: &str,
    item_id: &str,
    placement: ToolbarItemPlacement,
    shows_by_default: bool,
    content: &ViewHandle,
) -> ViewHandle {
    unsafe {
        let placement_meta = abi::metadata0(resolve::toolbar_placement_ma());
        let mut placement =
            static_getter_indirect(placement.resolved(), placement_meta, 0);

        let item_ctx = Box::leak(Box::new(abi::ClosureCtx::new(
            emit_tuple,
            Child::leak(content, Content::Erased),
        ))) as *mut abi::ClosureCtx;

        let item_meta = abi::resolve_type_by_mangled_name(&TOOLBAR_ITEM_ID_T);
        assert!(!item_meta.is_null(), "ToolbarItem<String, AnyView> metadata");
        let mut item = vec![0u8; abi::type_bytes(item_meta)];
        let id = abi::swift_string(item_id);
        // ToolbarItem.init(id:placement:showsByDefault:content:)
        //   x0, x1 = id, x2 = @in placement, x3 = showsByDefault,
        //   x4 = content fn, x5 = content ctx, x6 = B.Type, x7 = B: View WT,
        //   x8 = @out. Exactly eight, so nothing spills.
        core::arch::asm!("blr {f}", f = in(reg) resolve::toolbar_item_id_init(),
            in("x0") u64::from_le_bytes(id[..8].try_into().expect("id lo")),
            in("x1") u64::from_le_bytes(id[8..].try_into().expect("id hi")),
            in("x2") placement.as_mut_ptr(),
            in("x3") u64::from(shows_by_default),
            in("x4") abi::swift_closure_thunk as *const c_void,
            in("x5") item_ctx,
            in("x6") resolve::anyview_meta(),
            in("x7") resolve::anyview_wt(),
            in("x8") item.as_mut_ptr(),
            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
            lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
            clobber_abi("C"),
        );

        // `buildBlock` is the same one the plain toolbar uses — it is generic
        // over `ToolbarContent`, which the customizable item also is.
        let item_tc_wt = abi::get_view_wt_for_proto(item_meta, resolve::toolbar_content_proto());
        assert!(!item_tc_wt.is_null(), "ToolbarItem<String, _>: ToolbarContent");

        let tuple_meta = abi::resolve_type_by_mangled_name(&TOOLBAR_TUPLE_ID_T);
        assert!(!tuple_meta.is_null(), "TupleToolbarContent<ToolbarItem<String, _>> metadata");
        let mut tuple = vec![0u8; abi::type_bytes(tuple_meta)];
        core::arch::asm!("blr {f}", f = in(reg) resolve::toolbar_build_block(),
            in("x0") item.as_mut_ptr(),
            in("x1") item_meta,
            in("x2") item_tc_wt,
            in("x8") tuple.as_mut_ptr(),
            lateout("x3") _, lateout("x4") _, lateout("x5") _, lateout("x6") _,
            lateout("x7") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
            lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x20") _,
            lateout("x21") _, clobber_abi("C"),
        );

        // The conditional conformance: `TupleToolbarContent<T>` is customizable
        // when `T` is. Asking for it is what proves the item really is.
        let tuple_custom_wt = abi::get_view_wt_for_proto(
            tuple_meta,
            resolve::customizable_toolbar_content_proto(),
        );
        assert!(
            !tuple_custom_wt.is_null(),
            "TupleToolbarContent<ToolbarItem<String, _>>: CustomizableToolbarContent"
        );

        let leaked = Box::leak(tuple.into_boxed_slice());
        let bytes = Box::leak(Box::new(BytesPayload {
            data: leaked.as_ptr(),
            len: leaked.len(),
        }));
        let ctx = Box::leak(Box::new(abi::ClosureCtx::new(
            emit_bytes,
            bytes as *mut BytesPayload as *mut c_void,
        ))) as *mut abi::ClosureCtx;

        let tid = abi::swift_string(toolbar_id);
        // toolbar(id:content:) takes the id in x0/x1 and the closure in x2/x3,
        // and `generic1_modifier_ints` puts Self.Type, T.Type and the two
        // witnesses in x4–x7 after them. The result type comes from the opaque
        // return descriptor rather than a mangled name written out here.
        super::generic1_modifier_ints(
            resolve::toolbar_id_fn(),
            resolve::toolbar_id_oret(),
            view,
            tuple_meta,
            tuple_custom_wt,
            &[
                u64::from_le_bytes(tid[..8].try_into().expect("toolbar id lo")),
                u64::from_le_bytes(tid[8..].try_into().expect("toolbar id hi")),
                abi::swift_closure_thunk as *const c_void as u64,
                ctx as u64,
            ],
            [0.0; 4],
        )
    }
}

#[cfg(not(target_os = "macos"))]
swift_statics! {
    /// `SwiftUI.ContentToolbarPlacement` — where a content toolbar attaches.
    ///
    /// iOS only, and not by choice: every case is `@available(macOS,
    /// unavailable)` and SwiftUI ships trap stubs for all three on macOS.
    /// Measured by calling each getter, not inferred from the interface —
    /// `examples/traps.rs` is that measurement.
    pub enum ContentToolbarPlacement {
        NavigationSplitView = c"$s7SwiftUI23ContentToolbarPlacementV19navigationSplitViewACvgZ",
        TabViewAccessory = c"$s7SwiftUI23ContentToolbarPlacementV16tabViewAccessoryACvgZ",
        TabViewSidebar = c"$s7SwiftUI23ContentToolbarPlacementV14tabViewSidebarACvgZ",
    }
}

/// `.contentToolbar(for:content:)` — a toolbar attached to a container's
/// content rather than to the window.
///
/// The `Content: View` overload, which is the one that takes an ordinary view
/// builder; the `ToolbarContent` overload is a different symbol.
///
/// iOS only. See [`ContentToolbarPlacement`] — there is no macOS case to pass.
#[cfg(not(target_os = "macos"))]
pub fn content_toolbar(
    view: &ViewHandle,
    placement: ContentToolbarPlacement,
    content: &ViewHandle,
) -> ViewHandle {
    unsafe {
        let placement_meta = abi::metadata0(resolve::content_toolbar_placement_ma());
        assert!(!placement_meta.is_null(), "ContentToolbarPlacement metadata");
        let mut placement =
            static_getter_indirect(placement.resolved(), placement_meta, 0);

        // SwiftUI keeps the builder, so the context outlives this call.
        let ctx = Box::leak(Box::new(abi::ClosureCtx::new(
            emit_tuple,
            Child::leak(content, Content::Erased),
        ))) as *mut abi::ClosureCtx;

        let subst = [
            view.meta,
            resolve::anyview_meta(),
            view.wt,
            resolve::anyview_wt(),
        ];
        let (meta, wt, size) =
            super::opaque_result_type(resolve::content_toolbar_oret(), &subst);
        let mut result = vec![0u8; size];
        let this = view.value_ptr();
        // x0 = @in placement, x1/x2 = the builder, x3 = Self.Type,
        // x4 = Content.Type, x5 = Self: View WT, x6 = Content: View WT,
        // x8 = @out, x20 = self.
        core::arch::asm!("blr {f}", f = in(reg) resolve::content_toolbar_fn(),
            in("x0") placement.as_mut_ptr(),
            in("x1") abi::swift_closure_thunk as *const c_void,
            in("x2") ctx,
            in("x3") view.meta,
            in("x4") resolve::anyview_meta(),
            in("x5") view.wt,
            in("x6") resolve::anyview_wt(),
            in("x8") result.as_mut_ptr(),
            in("x20") this,
            lateout("x7") _,
            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
        );
        wrap_owned(result, meta, wt)
    }
}