swiftui-native 0.0.1

SwiftUI from pure Rust — no Swift bridge, no shim, only swift-runtime calls
Documentation
//! visionOS: Hover Effects (pure runtime via Swift ABI).

use super::*;
#[cfg(not(target_os = "macos"))]
use crate::abi;
use crate::resolve;
#[cfg(not(target_os = "macos"))]
use core::ffi::c_void;
/// Apply `.hoverEffect(.lift)` — visionOS native gaze-driven lift.
///
/// Constructs a `LiftHoverEffect` value at runtime via the static getter,
/// then calls `View.hoverEffect<A: CustomHoverEffect>(_:isEnabled:)` using
/// the Swift calling convention with metadata and witness tables resolved
/// from SwiftUI.framework symbols.
///
/// Returns the original view unchanged on platforms where symbols are absent.
/// Not available on macOS. SwiftUI exports the symbol on every platform but the
/// macOS implementation is a trap stub — calling it dies with "Unavailable code
/// reached", which no runtime check can see coming, so the binding is compiled
/// out instead of failing later.
#[cfg(not(target_os = "macos"))]
pub fn hover_effect_lift(view: &ViewHandle) -> ViewHandle {
    hover_effect_impl(view, resolve::lift_hover_getter, resolve::lift_hover_meta_acc)
}

/// Apply `.hoverEffect()` — the effect a bare call means, which is
/// `AutomaticHoverEffect`.
///
/// Not available on macOS, for the reason [`hover_effect_lift`] gives.
#[cfg(not(target_os = "macos"))]
pub fn hover_effect_automatic(view: &ViewHandle) -> ViewHandle {
    hover_effect_impl(view, resolve::automatic_hover_getter, resolve::automatic_hover_meta_acc)
}

/// Apply `.hoverEffect(.highlight)` — visionOS native gaze-driven highlight.
///
/// Same mechanism as `hover_effect_lift` but with `HighlightHoverEffect`.
/// Not available on macOS. SwiftUI exports the symbol on every platform but the
/// macOS implementation is a trap stub — calling it dies with "Unavailable code
/// reached", which no runtime check can see coming, so the binding is compiled
/// out instead of failing later.
#[cfg(not(target_os = "macos"))]
pub fn hover_effect_highlight(view: &ViewHandle) -> ViewHandle {
    hover_effect_impl(view, resolve::highlight_hover_getter, resolve::highlight_hover_meta_acc)
}

#[cfg(not(target_os = "macos"))]
pub(crate) fn hover_effect_impl(
    view: &ViewHandle,
    effect_getter: fn() -> *const c_void,
    effect_meta_acc: fn() -> *const c_void,
) -> ViewHandle {
    unsafe {
        // Check if symbols are available (visionOS only)
        let func = resolve::try_sym(c"$s7SwiftUI4ViewPAAE11hoverEffect_9isEnabledQrqd___SbtAA011CustomHoverE0Rd__lF");
        if func.is_null() {
            return view.clone();
        }

        // 1. Get effect type metadata via accessor: meta_acc(0) -> (metadata, status)
        let effect_meta: *const c_void;
        let _status: u64;
        core::arch::asm!("blr {f}",
            f = in(reg) effect_meta_acc(),
            in("x0") 0u64,  // request kind = complete
            lateout("x0") effect_meta,
            lateout("x1") _status,
            lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
            lateout("x6") _, lateout("x7") _, lateout("x8") _, lateout("x9") _,
            lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
            lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
            lateout("lr") _, clobber_abi("C"),
        );

        // 2. Get value witness table to learn the effect type size
        let vwt = &*swift_runtime_sys::SwiftABI::get_value_witness_table(effect_meta);
        let effect_size = vwt.size;

        // 3. Construct the effect value by calling the static getter
        //    Convention: (@thin EffectType.Type) -> @out Effect
        let mut effect_buf = vec![0u8; effect_size.max(8)];
        core::arch::asm!("blr {f}",
            f = in(reg) effect_getter(),
            in("x8") effect_buf.as_mut_ptr(), // @out sret
            lateout("x0") _, lateout("x1") _, lateout("x2") _, 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"),
        );

        // 4. A:CustomHoverEffect, looked up at run time.
        //
        //    This used to call a `…VAcA06CustomdE0AAWl` lazy accessor, and
        //    `hover_effect_highlight` passed *Lift*'s — a `LiftHoverEffect`
        //    witness alongside `HighlightHoverEffect` metadata. Neither accessor
        //    exists to call in the first place: SwiftUI exports the conformance
        //    descriptors and leaves the tables to clients.
        let effect_wt = super::conformance_wt(effect_meta, resolve::custom_hover_effect_proto());

        // 5. Resolve opaque return type metadata for the result.
        //    The result is `some View`, so its metadata and witness come from
        //    the opaque type descriptor.
        //
        //    This was hand-rolled against `swift_getOpaqueTypeConformance2` and
        //    asked for conformance **index 1**. A `-> some View` declares one
        //    conformance, at index 0; index 1 reads adjacent memory and hands
        //    back a witness table that is not one. It also fell back to a
        //    128-byte buffer when the metadata lookup failed, which would have
        //    scribbled past the end of whatever the real type is.
        //
        //    `opaque_result_type` is the crate's own path for this and gets both
        //    right.
        let subst = [
            view.meta,  // Self.Type = AnyView
            effect_meta,              // A.Type
            view.wt,    // Self: View WT
            effect_wt,                // A: CustomHoverEffect WT
        ];
        let (result_meta, result_view_wt, result_size) =
            super::opaque_result_type(resolve::hover_effect_oret(), &subst);
        let mut result = vec![0u8; result_size.max(8)];

        // 6. Call View.hoverEffect<A>(_:isEnabled:)
        //    LLVM IR convention:
        //      sret result, @in effect, i1 isEnabled,
        //      Self.Type, A.Type, Self:View WT, A:CustomHoverEffect WT,
        //      swiftself &self_value
        let self_storage = view.value_ptr();
        core::arch::asm!("blr {func}",
            func = in(reg) func,
            in("x8") result.as_mut_ptr(),              // @out sret
            in("x0") effect_buf.as_ptr(),              // @in effect value
            in("x1") 1u64,                             // isEnabled = true
            in("x2") view.meta,          // Self.Type = AnyView
            in("x3") effect_meta,                      // A.Type
            in("x4") view.wt,            // Self:View WT
            in("x5") effect_wt,                        // A:CustomHoverEffect WT
            in("x20") self_storage,   // swiftself @in_guaranteed
            lateout("x0") _, lateout("x1") _, lateout("x2") _, 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") _,
            clobber_abi("C"),
        );

        // 7. Wrap the result in AnyView using the resolved metadata + View WT
        ViewHandle::wrap(
            result.as_ptr() as _, result_meta, result_view_wt,
        )
    }
}

/// Apply `.onHover(perform:)`.
///
/// On macOS this fires on mouse hover; on visionOS, when the user's gaze lands
/// on the view. The callback receives `true` on enter and `false` on exit.
pub fn on_hover(view: &ViewHandle, callback: impl Fn(bool) + 'static) -> ViewHandle {
    unsafe {
        opaque_modifier(
            resolve::on_hover_fn(),
            resolve::on_hover_oret(),
            view,
            &bool_words(callback),
            [0.0; 4],
        )
    }
}

/// Apply `.defaultHoverEffect(_:)` with `.highlight`.
///
/// Marked `@available(macOS, unavailable)`, which is a *source* restriction:
/// the compiler refuses the call, but `$s7SwiftUI4ViewPAAE18defaultHoverEffect…`
/// is in the export table all the same. Searching for the symbol and finding it
/// is not evidence the call works — the macOS implementation is a trap stub,
/// the same as `hoverEffect` next door — so this is compiled out there rather
/// than left to die at run time.
///
/// The argument is `HoverEffect?`, and `HoverEffect` is resilient: `.highlight`
/// comes back through x8 and is then handed over by address.
#[cfg(not(target_os = "macos"))]
pub fn default_hover_effect_highlight(view: &ViewHandle) -> ViewHandle {
    default_hover_effect(view, HoverEffect::Highlight)
}

/// Which of `SwiftUI.HoverEffect`'s three effects a view defaults to.
///
/// Not the same type as the one [`hover_effect_highlight`] takes: that
/// modifier is generic over `CustomHoverEffect`, whose conformers are separate
/// types (`HighlightHoverEffect` and friends). This is the concrete enum-like
/// struct `defaultHoverEffect(_:)` wants.
#[cfg(not(target_os = "macos"))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HoverEffect {
    /// Let the system pick, which is what a view gets with no modifier at all.
    Automatic,
    /// Morph the pointer into the view's shape.
    Highlight,
    /// Float the view up towards the pointer.
    Lift,
}

/// `.defaultHoverEffect(_:)` with the effect named.
///
/// See [`default_hover_effect_highlight`] for why this is iOS-only.
#[cfg(not(target_os = "macos"))]
pub fn default_hover_effect(view: &ViewHandle, effect: HoverEffect) -> ViewHandle {
    unsafe {
        let getter = match effect {
            HoverEffect::Automatic => resolve::hover_effect_automatic_static(),
            HoverEffect::Highlight => resolve::hover_effect_highlight_static(),
            HoverEffect::Lift => resolve::hover_effect_lift_static(),
        };
        let meta = abi::metadata0(resolve::hover_effect_ma());
        assert!(!meta.is_null(), "HoverEffect metadata");
        let effect = super::static_getter_indirect(
            getter,
            meta,
            abi::type_bytes(meta),
        );
        let opt = abi::resolve_type_by_mangled_name(b"7SwiftUI11HoverEffectVSg");
        assert!(!opt.is_null(), "Optional<HoverEffect>");
        let mut arg = vec![0u8; abi::type_bytes(opt)];
        let n = effect.len().min(arg.len());
        arg[..n].copy_from_slice(&effect[..n]);

        super::opaque_modifier(
            resolve::default_hover_effect_fn(),
            resolve::default_hover_effect_oret(),
            view,
            &[arg.as_ptr() as u64],
            [0.0; 4],
        )
    }
}