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}