Skip to main content

swiftui_native/views/
hover.rs

1//! visionOS: Hover Effects (pure runtime via Swift ABI).
2
3use super::*;
4#[cfg(not(target_os = "macos"))]
5use crate::abi;
6use crate::resolve;
7#[cfg(not(target_os = "macos"))]
8use core::ffi::c_void;
9/// Apply `.hoverEffect(.lift)` — visionOS native gaze-driven lift.
10///
11/// Constructs a `LiftHoverEffect` value at runtime via the static getter,
12/// then calls `View.hoverEffect<A: CustomHoverEffect>(_:isEnabled:)` using
13/// the Swift calling convention with metadata and witness tables resolved
14/// from SwiftUI.framework symbols.
15///
16/// Returns the original view unchanged on platforms where symbols are absent.
17/// Not available on macOS. SwiftUI exports the symbol on every platform but the
18/// macOS implementation is a trap stub — calling it dies with "Unavailable code
19/// reached", which no runtime check can see coming, so the binding is compiled
20/// out instead of failing later.
21#[cfg(not(target_os = "macos"))]
22pub fn hover_effect_lift(view: &ViewHandle) -> ViewHandle {
23    hover_effect_impl(view, resolve::lift_hover_getter, resolve::lift_hover_meta_acc)
24}
25
26/// Apply `.hoverEffect()` — the effect a bare call means, which is
27/// `AutomaticHoverEffect`.
28///
29/// Not available on macOS, for the reason [`hover_effect_lift`] gives.
30#[cfg(not(target_os = "macos"))]
31pub fn hover_effect_automatic(view: &ViewHandle) -> ViewHandle {
32    hover_effect_impl(view, resolve::automatic_hover_getter, resolve::automatic_hover_meta_acc)
33}
34
35/// Apply `.hoverEffect(.highlight)` — visionOS native gaze-driven highlight.
36///
37/// Same mechanism as `hover_effect_lift` but with `HighlightHoverEffect`.
38/// Not available on macOS. SwiftUI exports the symbol on every platform but the
39/// macOS implementation is a trap stub — calling it dies with "Unavailable code
40/// reached", which no runtime check can see coming, so the binding is compiled
41/// out instead of failing later.
42#[cfg(not(target_os = "macos"))]
43pub fn hover_effect_highlight(view: &ViewHandle) -> ViewHandle {
44    hover_effect_impl(view, resolve::highlight_hover_getter, resolve::highlight_hover_meta_acc)
45}
46
47#[cfg(not(target_os = "macos"))]
48pub(crate) fn hover_effect_impl(
49    view: &ViewHandle,
50    effect_getter: fn() -> *const c_void,
51    effect_meta_acc: fn() -> *const c_void,
52) -> ViewHandle {
53    unsafe {
54        // Check if symbols are available (visionOS only)
55        let func = resolve::try_sym(c"$s7SwiftUI4ViewPAAE11hoverEffect_9isEnabledQrqd___SbtAA011CustomHoverE0Rd__lF");
56        if func.is_null() {
57            return view.clone();
58        }
59
60        // 1. Get effect type metadata via accessor: meta_acc(0) -> (metadata, status)
61        let effect_meta: *const c_void;
62        let _status: u64;
63        core::arch::asm!("blr {f}",
64            f = in(reg) effect_meta_acc(),
65            in("x0") 0u64,  // request kind = complete
66            lateout("x0") effect_meta,
67            lateout("x1") _status,
68            lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
69            lateout("x6") _, lateout("x7") _, lateout("x8") _, lateout("x9") _,
70            lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
71            lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
72            lateout("lr") _, clobber_abi("C"),
73        );
74
75        // 2. Get value witness table to learn the effect type size
76        let vwt = &*swift_runtime_sys::SwiftABI::get_value_witness_table(effect_meta);
77        let effect_size = vwt.size;
78
79        // 3. Construct the effect value by calling the static getter
80        //    Convention: (@thin EffectType.Type) -> @out Effect
81        let mut effect_buf = vec![0u8; effect_size.max(8)];
82        core::arch::asm!("blr {f}",
83            f = in(reg) effect_getter(),
84            in("x8") effect_buf.as_mut_ptr(), // @out sret
85            lateout("x0") _, lateout("x1") _, lateout("x2") _, lateout("x3") _,
86            lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
87            lateout("x9") _, lateout("x10") _, lateout("x11") _,
88            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
89            lateout("x16") _, lateout("x17") _, lateout("lr") _,
90            lateout("x20") _, lateout("x21") _,
91            clobber_abi("C"),
92        );
93
94        // 4. A:CustomHoverEffect, looked up at run time.
95        //
96        //    This used to call a `…VAcA06CustomdE0AAWl` lazy accessor, and
97        //    `hover_effect_highlight` passed *Lift*'s — a `LiftHoverEffect`
98        //    witness alongside `HighlightHoverEffect` metadata. Neither accessor
99        //    exists to call in the first place: SwiftUI exports the conformance
100        //    descriptors and leaves the tables to clients.
101        let effect_wt = super::conformance_wt(effect_meta, resolve::custom_hover_effect_proto());
102
103        // 5. Resolve opaque return type metadata for the result.
104        //    The result is `some View`, so its metadata and witness come from
105        //    the opaque type descriptor.
106        //
107        //    This was hand-rolled against `swift_getOpaqueTypeConformance2` and
108        //    asked for conformance **index 1**. A `-> some View` declares one
109        //    conformance, at index 0; index 1 reads adjacent memory and hands
110        //    back a witness table that is not one. It also fell back to a
111        //    128-byte buffer when the metadata lookup failed, which would have
112        //    scribbled past the end of whatever the real type is.
113        //
114        //    `opaque_result_type` is the crate's own path for this and gets both
115        //    right.
116        let subst = [
117            view.meta,  // Self.Type = AnyView
118            effect_meta,              // A.Type
119            view.wt,    // Self: View WT
120            effect_wt,                // A: CustomHoverEffect WT
121        ];
122        let (result_meta, result_view_wt, result_size) =
123            super::opaque_result_type(resolve::hover_effect_oret(), &subst);
124        let mut result = vec![0u8; result_size.max(8)];
125
126        // 6. Call View.hoverEffect<A>(_:isEnabled:)
127        //    LLVM IR convention:
128        //      sret result, @in effect, i1 isEnabled,
129        //      Self.Type, A.Type, Self:View WT, A:CustomHoverEffect WT,
130        //      swiftself &self_value
131        let self_storage = view.value_ptr();
132        core::arch::asm!("blr {func}",
133            func = in(reg) func,
134            in("x8") result.as_mut_ptr(),              // @out sret
135            in("x0") effect_buf.as_ptr(),              // @in effect value
136            in("x1") 1u64,                             // isEnabled = true
137            in("x2") view.meta,          // Self.Type = AnyView
138            in("x3") effect_meta,                      // A.Type
139            in("x4") view.wt,            // Self:View WT
140            in("x5") effect_wt,                        // A:CustomHoverEffect WT
141            in("x20") self_storage,   // swiftself @in_guaranteed
142            lateout("x0") _, lateout("x1") _, lateout("x2") _, lateout("x3") _,
143            lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
144            lateout("x9") _, lateout("x10") _, lateout("x11") _,
145            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
146            lateout("x16") _, lateout("x17") _, lateout("lr") _,
147            clobber_abi("C"),
148        );
149
150        // 7. Wrap the result in AnyView using the resolved metadata + View WT
151        ViewHandle::wrap(
152            result.as_ptr() as _, result_meta, result_view_wt,
153        )
154    }
155}
156
157/// Apply `.onHover(perform:)`.
158///
159/// On macOS this fires on mouse hover; on visionOS, when the user's gaze lands
160/// on the view. The callback receives `true` on enter and `false` on exit.
161pub fn on_hover(view: &ViewHandle, callback: impl Fn(bool) + 'static) -> ViewHandle {
162    unsafe {
163        opaque_modifier(
164            resolve::on_hover_fn(),
165            resolve::on_hover_oret(),
166            view,
167            &bool_words(callback),
168            [0.0; 4],
169        )
170    }
171}
172
173/// Apply `.defaultHoverEffect(_:)` with `.highlight`.
174///
175/// Marked `@available(macOS, unavailable)`, which is a *source* restriction:
176/// the compiler refuses the call, but `$s7SwiftUI4ViewPAAE18defaultHoverEffect…`
177/// is in the export table all the same. Searching for the symbol and finding it
178/// is not evidence the call works — the macOS implementation is a trap stub,
179/// the same as `hoverEffect` next door — so this is compiled out there rather
180/// than left to die at run time.
181///
182/// The argument is `HoverEffect?`, and `HoverEffect` is resilient: `.highlight`
183/// comes back through x8 and is then handed over by address.
184#[cfg(not(target_os = "macos"))]
185pub fn default_hover_effect_highlight(view: &ViewHandle) -> ViewHandle {
186    default_hover_effect(view, HoverEffect::Highlight)
187}
188
189/// Which of `SwiftUI.HoverEffect`'s three effects a view defaults to.
190///
191/// Not the same type as the one [`hover_effect_highlight`] takes: that
192/// modifier is generic over `CustomHoverEffect`, whose conformers are separate
193/// types (`HighlightHoverEffect` and friends). This is the concrete enum-like
194/// struct `defaultHoverEffect(_:)` wants.
195#[cfg(not(target_os = "macos"))]
196#[derive(Clone, Copy, Debug, PartialEq, Eq)]
197pub enum HoverEffect {
198    /// Let the system pick, which is what a view gets with no modifier at all.
199    Automatic,
200    /// Morph the pointer into the view's shape.
201    Highlight,
202    /// Float the view up towards the pointer.
203    Lift,
204}
205
206/// `.defaultHoverEffect(_:)` with the effect named.
207///
208/// See [`default_hover_effect_highlight`] for why this is iOS-only.
209#[cfg(not(target_os = "macos"))]
210pub fn default_hover_effect(view: &ViewHandle, effect: HoverEffect) -> ViewHandle {
211    unsafe {
212        let getter = match effect {
213            HoverEffect::Automatic => resolve::hover_effect_automatic_static(),
214            HoverEffect::Highlight => resolve::hover_effect_highlight_static(),
215            HoverEffect::Lift => resolve::hover_effect_lift_static(),
216        };
217        let meta = abi::metadata0(resolve::hover_effect_ma());
218        assert!(!meta.is_null(), "HoverEffect metadata");
219        let effect = super::static_getter_indirect(
220            getter,
221            meta,
222            abi::type_bytes(meta),
223        );
224        let opt = abi::resolve_type_by_mangled_name(b"7SwiftUI11HoverEffectVSg");
225        assert!(!opt.is_null(), "Optional<HoverEffect>");
226        let mut arg = vec![0u8; abi::type_bytes(opt)];
227        let n = effect.len().min(arg.len());
228        arg[..n].copy_from_slice(&effect[..n]);
229
230        super::opaque_modifier(
231            resolve::default_hover_effect_fn(),
232            resolve::default_hover_effect_oret(),
233            view,
234            &[arg.as_ptr() as u64],
235            [0.0; 4],
236        )
237    }
238}