Skip to main content

swiftui_native/views/
accessibility.rs

1//! Accessibility, focus, lifecycle, gestures, geometry.
2//!
3//! * Accessibility attributes, traits, and focus
4
5use super::*;
6use crate::abi;
7use crate::resolve;
8use core::ffi::c_void;
9use crate::mangle::{self, swift_type};
10
11// ── Accessibility, focus, lifecycle, gestures, geometry ──
12
13mangle::result_types! {
14    /// `GeometryReader<AnyView>`
15    pub(crate) GEOMETRY_T = ty("GeometryReader").of(vec![ty("AnyView")]);
16}
17mangle::modifier_types! {
18    /// `ModifiedContent<AnyView, AccessibilityAttachmentModifier>`
19    pub(crate) A11Y_T = ty("AccessibilityAttachmentModifier");
20    /// `ModifiedContent<AnyView, _FocusableModifier>`
21    pub(crate) FOCUSABLE_T = ty("_FocusableModifier");
22    /// `ModifiedContent<AnyView, AddGestureModifier<LongPressGesture, DefaultGestureCombiner>>`
23    pub(crate) LONG_PRESS_T = ty("AddGestureModifier").of(vec![ty("LongPressGesture"), ty("DefaultGestureCombiner")]);
24}
25
26
27
28
29/// Apply `.accessibilityLabel(_:)`.
30pub fn accessibility_label(view: &ViewHandle, label: &str) -> ViewHandle {
31    unsafe {
32        let ss = abi::swift_string(label);
33        let s0 = u64::from_le_bytes(ss[..8].try_into().unwrap());
34        let s1 = u64::from_le_bytes(ss[8..].try_into().unwrap());
35        let (k0, k1, k2, k3) = localized_string_key(s0, s1);
36        let (_, _, size) = A11Y_T.applied_to(view.meta);
37        let r = abi::call_modifier(resolve::a11y_label_fn(), view.value_ptr(), view.meta, view.wt, &[k0, k1, k2, k3], [0.0; 4], size);
38        wrap_modified(r, &A11Y_T, view)
39    }
40}
41
42/// Apply `.accessibilityHidden(_:)`.
43pub fn accessibility_hidden(view: &ViewHandle, hidden: bool) -> ViewHandle {
44    unsafe {
45        let (_, _, size) = A11Y_T.applied_to(view.meta);
46        let r =
47            abi::call_modifier(resolve::a11y_hidden_fn(), view.value_ptr(), view.meta, view.wt, &[u64::from(hidden)], [0.0; 4], size);
48        wrap_modified(r, &A11Y_T, view)
49    }
50}
51
52/// Apply `.focusable(_:)`.
53pub fn focusable(view: &ViewHandle, value: bool) -> ViewHandle {
54    unsafe {
55        let (_, _, size) = FOCUSABLE_T.applied_to(view.meta);
56        let r =
57            abi::call_modifier(resolve::focusable_fn(), view.value_ptr(), view.meta, view.wt, &[u64::from(value)], [0.0; 4], size);
58        wrap_modified(r, &FOCUSABLE_T, view)
59    }
60}
61
62/// Apply `.onDisappear(perform:)`.
63pub fn on_disappear(view: &ViewHandle, action: impl Fn() + 'static) -> ViewHandle {
64    unsafe {
65        let ctx = Box::leak(Box::new(abi::ClosureCtx::new(
66            invoke_action,
67            boxed_ctx(Box::new(action) as Box<dyn Fn()>),
68        ))) as *mut abi::ClosureCtx;
69        let (_, _, size) = ON_APPEAR_T.applied_to(view.meta);
70        let r = abi::call_modifier(resolve::on_disappear_fn(), view.value_ptr(), view.meta, view.wt,
71            &[abi::swift_closure_thunk as *const () as u64, ctx as u64],
72            [0.0; 4],
73            size,
74        );
75        wrap_modified(r, &ON_APPEAR_T, view)
76    }
77}
78
79/// Attach a `LongPressGesture` with `.gesture(_:including:)`.
80pub fn long_press_gesture(view: &ViewHandle, duration: f64, max_distance: f64) -> ViewHandle {
81    unsafe {
82        let g_meta = abi::metadata0(resolve::long_press_ma());
83        let mut gesture = vec![0u8; abi::type_bytes(g_meta)];
84        // LongPressGesture.init(minimumDuration:maximumDistance:)
85        core::arch::asm!("blr {f}", f = in(reg) resolve::long_press_init(),
86            in("d0") duration, in("d1") max_distance,
87            in("x8") gesture.as_mut_ptr(),
88            in("x20") g_meta,
89            lateout("x0") _, lateout("x1") _, lateout("x2") _, lateout("x3") _,
90            lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
91            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
92            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
93            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
94        );
95
96        let g_wt = abi::get_view_wt_for_proto(g_meta, resolve::gesture_proto());
97        assert!(!g_wt.is_null(), "LongPressGesture: Gesture");
98
99        let (_, _, size) = LONG_PRESS_T.applied_to(view.meta);
100        let mut result = vec![0u8; size];
101        let this = view.value_ptr();
102        // gesture<G: Gesture>(_: G, including: GestureMask)
103        //   x0 = @in gesture, x1 = GestureMask (.all == 3),
104        //   x2 = Self.Type, x3 = G.Type, x4 = Self: View WT, x5 = G: Gesture WT
105        core::arch::asm!("blr {f}", f = in(reg) resolve::gesture_fn(),
106            in("x0") gesture.as_mut_ptr(),
107            in("x1") 3u64,
108            in("x2") view.meta,
109            in("x3") g_meta,
110            in("x4") view.wt,
111            in("x5") g_wt,
112            in("x8") result.as_mut_ptr(),
113            in("x20") this,
114            lateout("x6") _, lateout("x7") _,
115            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
116            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
117            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
118        );
119        wrap_modified(result, &LONG_PRESS_T, view)
120    }
121}
122
123/// The Rust side of a `GeometryReader` content closure, given the measured size.
124pub(crate) type GeometryBuilder = Box<dyn Fn(f64, f64) -> ViewHandle>;
125
126/// Read `GeometryProxy.size`.
127///
128/// `GeometryProxy` is 52 bytes and address-only, so it arrives by pointer; the
129/// getter returns a `CGSize`, which is two doubles in d0/d1.
130pub(crate) unsafe fn geometry_proxy_size(proxy: *const u8) -> (f64, f64) {
131    let (w, h): (f64, f64);
132    unsafe {
133        core::arch::asm!("blr {f}", f = in(reg) resolve::geometry_proxy_size(),
134            in("x20") proxy,
135            lateout("d0") w, lateout("d1") h,
136            lateout("x0") _, lateout("x1") _, lateout("x2") _, lateout("x3") _,
137            lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
138            lateout("x8") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
139            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
140            lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x21") _,
141            clobber_abi("C"),
142        );
143    }
144    (w, h)
145}
146
147/// A builder together with the content type it was found to produce.
148///
149/// Swift knows a `GeometryReader`'s content type at compile time — the IR shows
150/// the closure lowered to `(@out Text, @in GeometryProxy)`, with `Text` written
151/// straight into the result — which is why the instantiation is
152/// `GeometryReader<Text>` and not `GeometryReader<AnyView>`. A Rust closure
153/// returning `ViewHandle` has no such type to read off, so it is *asked*: the
154/// builder is run once while constructing, and whatever it produced fixes the
155/// instantiation.
156pub(crate) struct TypedBuilder {
157    pub(crate) builder: GeometryBuilder,
158    /// What the probe produced. Every later call has to agree.
159    pub(crate) meta: *const c_void,
160}
161
162/// `(GeometryProxy) -> Content` — measure the proxy, then build from Rust.
163pub(crate) unsafe extern "C" fn emit_geometry_view(out: *mut u8, proxy: i64, payload: *mut c_void) {
164    let typed = unsafe { &*(payload as *const TypedBuilder) };
165    let (w, h) = unsafe { geometry_proxy_size(proxy as *const u8) };
166    let view = (typed.builder)(w, h);
167    unsafe {
168        if typed.meta.is_null() {
169            // No type was promised, so the content is an `AnyView` and the box
170            // is the value.
171            *(out as *mut u64) = view.into_anyview_word();
172            return;
173        }
174        // A type *was* promised, and SwiftUI has sized its storage for it.
175        // Writing anything else would scribble past that, so this is checked
176        // rather than assumed — the same rule Swift enforces at compile time by
177        // making the closure's return type `some View`, i.e. exactly one type.
178        assert_eq!(
179            view.meta, typed.meta,
180            "a GeometryReader's builder returned {} after returning {} — SwiftUI \
181             sized the reader for the first. Use `view_if!` for a branch, which \
182             builds a `_ConditionalContent` and so stays one type.",
183            crate::diag::type_name(view.meta),
184            crate::diag::type_name(typed.meta),
185        );
186        let vwt = &*swift_runtime_sys::SwiftABI::get_value_witness_table(view.meta);
187        if vwt.size > 0 {
188            (vwt.initialize_with_copy)(out as *mut c_void, view.value_ptr() as *mut c_void, view.meta);
189        }
190    }
191}
192
193/// `GeometryReader { size in … }`.
194///
195/// The builder receives the measured width and height, which is the whole point
196/// of a `GeometryReader` — a version that discarded the proxy would just be a
197/// container. The closure escapes, since SwiftUI re-runs it whenever the
198/// geometry changes, so its context is leaked.
199pub fn geometry_reader(builder: impl Fn(f64, f64) -> ViewHandle + 'static) -> ViewHandle {
200    unsafe {
201        let boxed: GeometryBuilder = Box::new(builder);
202        // Ask the builder what it makes. A zero-sized proxy is what a reader is
203        // handed before it has been laid out, so this is a call it will see
204        // anyway — but it *is* an extra call, and a builder with side effects
205        // will notice.
206        let probe = boxed(0.0, 0.0);
207        let (known_meta, known_wt, _) = result_type(&GEOMETRY_T);
208        let typed = abi::generic_metadata_from(known_meta, &[probe.meta, probe.wt]);
209        let typed_wt = if typed.is_null() { core::ptr::null() } else { abi::get_view_wt(typed) };
210        let promised = if typed_wt.is_null() { core::ptr::null() } else { probe.meta };
211        let (meta, wt) = if promised.is_null() {
212            (known_meta, known_wt)
213        } else {
214            (typed, typed_wt)
215        };
216        let content_meta = if promised.is_null() { resolve::anyview_meta() } else { probe.meta };
217        let content_wt = if promised.is_null() { resolve::anyview_wt() } else { probe.wt };
218        drop(probe);
219
220        let payload =
221            Box::into_raw(Box::new(TypedBuilder { builder: boxed, meta: promised })) as *mut c_void;
222        let ctx = Box::leak(Box::new(abi::IndexClosureCtx::new(emit_geometry_view, payload)))
223            as *mut abi::IndexClosureCtx;
224
225        // `GeometryReader<AnyView>` is sixteen bytes, so it comes back *in
226        // registers* rather than through an indirect `x8` result. Supplying an
227        // out-pointer here leaves the buffer untouched.
228        let (r0, r1): (u64, u64);
229        core::arch::asm!("blr {f}", f = in(reg) resolve::geometry_reader_init(),
230            inout("x0") abi::index_closure_thunk as *const () as u64 => r0,
231            inout("x1") ctx as u64 => r1,
232            in("x2") content_meta,
233            in("x3") content_wt,
234            lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
235            lateout("x8") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
236            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
237            lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x20") _,
238            lateout("x21") _, clobber_abi("C"),
239        );
240        let mut result = [0u8; 16];
241        result[..8].copy_from_slice(&r0.to_le_bytes());
242        result[8..].copy_from_slice(&r1.to_le_bytes());
243        // An array on the stack, not a buffer the callee filled: this one has
244        // to be copied into the handle.
245        wrap_buf(&result, meta, wt)
246    }
247}
248
249/// Apply `.confirmationDialog(_:isPresented:titleVisibility:actions:)`.
250///
251/// Like `alert`, the actions builder escapes, and the generic arguments spill:
252/// the title, binding and visibility fill all eight registers.
253/// Shared tail of `confirmation_dialog` and `confirmation_dialog_bound`.
254///
255/// # Safety
256/// `binding` must be a `Binding<Bool>`.
257pub(crate) unsafe fn confirmation_dialog_with_binding(
258    view: &ViewHandle,
259    title: &str, actions: &ViewHandle,
260    binding: &[u8],
261) -> ViewHandle {
262    unsafe {
263        let ss = abi::swift_string(title);
264        let s0 = u64::from_le_bytes(ss[..8].try_into().unwrap());
265        let s1 = u64::from_le_bytes(ss[8..].try_into().unwrap());
266        let (k0, k1, k2, k3) = localized_string_key(s0, s1);
267        let b0 = u64::from_le_bytes(binding[0..8].try_into().unwrap());
268        let b1 = u64::from_le_bytes(binding[8..16].try_into().unwrap());
269        let b2 = binding[16] as u64;
270
271        let ctx = Box::leak(Box::new(abi::ClosureCtx::new(
272            emit_tuple,
273            Child::leak(actions, Content::Concrete),
274        ))) as *mut abi::ClosureCtx;
275
276        let subst = [
277            view.meta,                        // Self.Type — the concrete receiver
278            actions.meta,          // content — the closure emits AnyView
279            view.wt,                          // Self: View
280            actions.wt,            // content: View
281        ];
282        let (meta, wt, size) = opaque_result_type(resolve::confirm_oret(), &subst);
283        let mut result = vec![0u8; size];
284        let this = view.value_ptr();
285        // confirmationDialog(_:isPresented:titleVisibility:actions:)
286        //   x0-x3 = key, x4-x6 = Binding<Bool>, x7 = Visibility (.automatic == 0),
287        //   [sp] = actions fn, [sp+8] = actions ctx, [sp+16] = Self.Type,
288        //   [sp+24] = A.Type, [sp+32] = Self: View WT, [sp+40] = A: View WT
289        core::arch::asm!(
290            "sub sp, sp, #48",
291            "stp {fnp}, {cx}, [sp]",
292            "stp {sm}, {am}, [sp, #16]",
293            "stp {sw}, {aw}, [sp, #32]",
294            "blr {f}",
295            "add sp, sp, #48",
296            f = in(reg) resolve::confirm_fn(),
297            fnp = in(reg) abi::swift_closure_thunk as *const c_void,
298            cx = in(reg) ctx,
299            sm = in(reg) view.meta,
300            am = in(reg) actions.meta,
301            sw = in(reg) view.wt,
302            aw = in(reg) actions.wt,
303            in("x0") k0, in("x1") k1, in("x2") k2, in("x3") k3,
304            in("x4") b0, in("x5") b1, in("x6") b2,
305            in("x7") 0u64,
306            in("x8") result.as_mut_ptr(),
307            in("x20") this,
308            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
309            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
310            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
311        );
312        wrap_owned(result, meta, wt)
313    }
314}
315
316/// `confirmationDialog(_:isPresented:titleVisibility:actions:message:)`.
317///
318/// The message is the explanatory line under the title, and on a sheet-style
319/// dialog it is often the only place the consequence is stated — "This will
320/// delete 12 items" under a bare "Delete". Only the title-and-buttons overload
321/// was bound.
322///
323/// # Safety
324/// `binding` must be a live `Binding<Bool>`.
325pub(crate) unsafe fn confirmation_message_with_binding(
326    view: &ViewHandle,
327    title: &str,
328    actions: &ViewHandle,
329    message: &ViewHandle,
330    binding: &[u8],
331) -> ViewHandle {
332    unsafe {
333        let ss = abi::swift_string(title);
334        let s0 = u64::from_le_bytes(ss[..8].try_into().expect("title lo"));
335        let s1 = u64::from_le_bytes(ss[8..].try_into().expect("title hi"));
336        let (k0, k1, k2, k3) = super::primitives::localized_string_key(s0, s1);
337        let b0 = u64::from_le_bytes(binding[0..8].try_into().expect("binding 0"));
338        let b1 = u64::from_le_bytes(binding[8..16].try_into().expect("binding 1"));
339        let b2 = binding[16] as u64;
340
341        let actions_ctx = Box::leak(Box::new(abi::ClosureCtx::new(
342            emit_tuple,
343            Child::leak(actions, Content::Concrete),
344        ))) as *mut abi::ClosureCtx;
345        let message_ctx = Box::leak(Box::new(abi::ClosureCtx::new(
346            emit_tuple,
347            Child::leak(message, Content::Concrete),
348        ))) as *mut abi::ClosureCtx;
349
350        let subst = [view.meta, actions.meta, message.meta, view.wt, actions.wt, message.wt];
351        let (meta, wt, size) = opaque_result_type(resolve::confirm_msg_oret(), &subst);
352        let mut result = vec![0u8; size];
353        let this = view.value_ptr();
354        // confirmationDialog(_:isPresented:titleVisibility:actions:message:)
355        //   x0-x3 = key, x4-x6 = Binding<Bool>, x7 = Visibility (.automatic),
356        //   [sp]    = actions fn,  [sp+8]  = actions ctx,
357        //   [sp+16] = message fn,  [sp+24] = message ctx,
358        //   [sp+32] = Self.Type,   [sp+40] = A.Type,    [sp+48] = B.Type,
359        //   [sp+56] = Self: View,  [sp+64] = A: View,   [sp+72] = B: View
360        core::arch::asm!(
361            "sub sp, sp, #80",
362            "stp {af}, {ac}, [sp]",
363            "stp {mf}, {mc}, [sp, #16]",
364            "stp {sm}, {am}, [sp, #32]",
365            "stp {mm}, {sw}, [sp, #48]",
366            "stp {aw}, {mw}, [sp, #64]",
367            "blr {f}",
368            "add sp, sp, #80",
369            f = in(reg) resolve::confirm_msg_fn(),
370            af = in(reg) abi::swift_closure_thunk as *const c_void,
371            ac = in(reg) actions_ctx,
372            mf = in(reg) abi::swift_closure_thunk as *const c_void,
373            mc = in(reg) message_ctx,
374            sm = in(reg) subst[0],
375            am = in(reg) subst[1],
376            mm = in(reg) subst[2],
377            sw = in(reg) subst[3],
378            aw = in(reg) subst[4],
379            mw = in(reg) subst[5],
380            in("x0") k0, in("x1") k1, in("x2") k2, in("x3") k3,
381            in("x4") b0, in("x5") b1, in("x6") b2,
382            in("x7") 0u64,
383            in("x8") result.as_mut_ptr(),
384            in("x20") this,
385            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
386            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
387            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
388        );
389        wrap_owned(result, meta, wt)
390    }
391}
392
393/// `confirmationDialog(…actions:message:)` with a fixed flag, which cannot be
394/// dismissed — see [`confirmation_dialog`].
395pub fn confirmation_message(
396    view: &ViewHandle,
397    title: &str,
398    actions: &ViewHandle,
399    message: &ViewHandle,
400    presented: bool,
401) -> ViewHandle {
402    unsafe {
403        let binding = constant_binding(
404            &[u8::from(presented)],
405            resolve::bool_meta(),
406            mangle::bound(&swift_type!(SwiftUI.Binding), &[mangle::BOOL]).as_bytes(),
407        );
408        confirmation_message_with_binding(view, title, actions, message, &binding)
409    }
410}
411
412/// `confirmationDialog(…actions:message:)` bound to Rust state.
413pub fn confirmation_message_bound(
414    view: &ViewHandle,
415    title: &str,
416    actions: &ViewHandle,
417    message: &ViewHandle,
418    state: &BoundState<bool>,
419) -> ViewHandle {
420    unsafe {
421        let binding = bool_binding_of(state);
422        confirmation_message_with_binding(view, title, actions, message, &binding)
423    }
424}
425
426/// `confirmation_dialog` with a fixed flag. It cannot be dismissed: SwiftUI treats a
427/// `Binding.constant` as read-only, so the presentation has no way to write
428/// `false` back. Use [`confirmation_dialog_bound`] for one the user can close.
429pub fn confirmation_dialog(view: &ViewHandle, title: &str, actions: &ViewHandle, presented: bool) -> ViewHandle {
430    unsafe {
431        let binding = constant_binding(
432            &[u8::from(presented)],
433            resolve::bool_meta(),
434            mangle::bound(&swift_type!(SwiftUI.Binding), &[mangle::BOOL]).as_bytes(),
435        );
436        confirmation_dialog_with_binding(view, title, actions, &binding)
437    }
438}
439
440/// `confirmation_dialog` bound to Rust state, so dismissing it writes `false` back.
441pub fn confirmation_dialog_bound(view: &ViewHandle, title: &str, actions: &ViewHandle, state: &BoundState<bool>) -> ViewHandle {
442    unsafe {
443        let binding = bool_binding_of(state);
444        confirmation_dialog_with_binding(view, title, actions, &binding)
445    }
446}
447
448
449// ── Accessibility attributes, traits, and focus ──
450
451//
452// This family returns `ModifiedContent<Self, AccessibilityAttachmentModifier>`
453// outright rather than `some View`, so there is no opaque descriptor to consult
454// and the result type is the same for all of them: [`&A11Y_T`].
455
456/// Apply an accessibility modifier, whose result type never varies.
457pub(crate) unsafe fn a11y_modifier(
458    f: *const c_void,
459    v: &ViewHandle,
460    ints: &[u64],
461    floats: [f64; 4],
462) -> ViewHandle {
463    unsafe {
464        let (_, _, size) = A11Y_T.applied_to(v.meta);
465        let r = abi::call_modifier(f, v.value_ptr(), v.meta, v.wt, ints, floats, size);
466        wrap_modified(r, &A11Y_T, v)
467    }
468}
469
470/// Read a resilient type's `static var`, which returns through x8.
471///
472/// `AccessibilityTraits` and its neighbours are public but not `@frozen`, so
473/// their size is not known to a caller compiled against the SDK and every one
474/// of them travels by address. That applies to the static members as much as to
475/// the arguments: the getter takes the metatype in x20 and writes the value
476/// into the buffer x8 points at.
477pub(crate) unsafe fn resilient_static(meta: *const c_void, getter: &'static core::ffi::CStr) -> Vec<u8> {
478    unsafe {
479        let f = resolve::sym(getter);
480        assert!(!f.is_null(), "missing static: {}", crate::diag::symbol_name(getter));
481        abi::static_out(f, meta, abi::type_bytes(meta))
482    }
483}
484
485/// A member of `SwiftUI.AccessibilityTraits`.
486#[derive(Clone, Copy, Debug, PartialEq, Eq)]
487pub enum AccessibilityTrait {
488    IsButton,
489    IsHeader,
490    IsLink,
491    IsImage,
492    IsModal,
493    IsSwitch,
494    IsTabBar,
495    IsToggle,
496    IsSelected,
497    PlaysSound,
498    IsTabButton,
499    IsBackButton,
500    IsStaticText,
501    IsKeyboardKey,
502    IsSearchField,
503    CausesPageTurn,
504    IsMathEquation,
505    IsSummaryElement,
506    UpdatesFrequently,
507    StartsMediaSession,
508    AllowsDirectInteraction,
509}
510
511impl AccessibilityTrait {
512    /// The static property getter that produces this trait.
513    pub(crate) fn getter(self) -> &'static core::ffi::CStr {
514        match self {
515            Self::IsButton => c"$s7SwiftUI19AccessibilityTraitsV8isButtonACvgZ",
516            Self::IsHeader => c"$s7SwiftUI19AccessibilityTraitsV8isHeaderACvgZ",
517            Self::IsLink => c"$s7SwiftUI19AccessibilityTraitsV6isLinkACvgZ",
518            Self::IsImage => c"$s7SwiftUI19AccessibilityTraitsV7isImageACvgZ",
519            Self::IsModal => c"$s7SwiftUI19AccessibilityTraitsV7isModalACvgZ",
520            Self::IsSwitch => c"$s7SwiftUI19AccessibilityTraitsV8isSwitchACvgZ",
521            Self::IsTabBar => c"$s7SwiftUI19AccessibilityTraitsV8isTabBarACvgZ",
522            Self::IsToggle => c"$s7SwiftUI19AccessibilityTraitsV8isToggleACvgZ",
523            Self::IsSelected => c"$s7SwiftUI19AccessibilityTraitsV10isSelectedACvgZ",
524            Self::PlaysSound => c"$s7SwiftUI19AccessibilityTraitsV10playsSoundACvgZ",
525            Self::IsTabButton => c"$s7SwiftUI19AccessibilityTraitsV11isTabButtonACvgZ",
526            Self::IsBackButton => c"$s7SwiftUI19AccessibilityTraitsV12isBackButtonACvgZ",
527            Self::IsStaticText => c"$s7SwiftUI19AccessibilityTraitsV12isStaticTextACvgZ",
528            Self::IsKeyboardKey => c"$s7SwiftUI19AccessibilityTraitsV13isKeyboardKeyACvgZ",
529            Self::IsSearchField => c"$s7SwiftUI19AccessibilityTraitsV13isSearchFieldACvgZ",
530            Self::CausesPageTurn => c"$s7SwiftUI19AccessibilityTraitsV14causesPageTurnACvgZ",
531            Self::IsMathEquation => c"$s7SwiftUI19AccessibilityTraitsV14isMathEquationACvgZ",
532            Self::IsSummaryElement => c"$s7SwiftUI19AccessibilityTraitsV16isSummaryElementACvgZ",
533            Self::UpdatesFrequently => c"$s7SwiftUI19AccessibilityTraitsV17updatesFrequentlyACvgZ",
534            Self::StartsMediaSession => c"$s7SwiftUI19AccessibilityTraitsV18startsMediaSessionACvgZ",
535            Self::AllowsDirectInteraction => {
536                c"$s7SwiftUI19AccessibilityTraitsV23allowsDirectInteractionACvgZ"
537            }
538        }
539    }
540}
541
542/// Build an `AccessibilityTraits` set from its members.
543///
544/// The all-zero pattern is the empty set, so an empty slice needs no call and
545/// each trait is folded in with `formUnion` — a mutating method, which takes
546/// the receiver by address in x20 like any other resilient `self`.
547pub(crate) unsafe fn traits_value(traits: &[AccessibilityTrait]) -> Vec<u8> {
548    unsafe {
549        let meta = abi::metadata0(resolve::a11y_traits_ma());
550        let mut acc = vec![0u8; abi::type_bytes(meta)];
551        let union = resolve::a11y_traits_form_union();
552        for t in traits {
553            let one = resilient_static(meta, t.getter());
554            abi::call_out(
555                union,
556                abi::ints(&[one.as_ptr() as u64]),
557                [0.0; 4],
558                acc.as_mut_ptr() as u64,
559                core::ptr::null_mut(),
560            );
561        }
562        acc
563    }
564}
565
566swift_cases! {
567    /// `SwiftUI.AccessibilityHeadingLevel`, a frozen enum that fits in a register.
568    pub enum HeadingLevel {
569        Unspecified = 0,
570        H1 = 1,
571        H2 = 2,
572        H3 = 3,
573        H4 = 4,
574        H5 = 5,
575        H6 = 6,
576    }
577}
578
579swift_statics! {
580    /// A member of `SwiftUI.AccessibilityTextContentType`.
581    pub enum TextContentType {
582        Plain = c"$s7SwiftUI28AccessibilityTextContentTypeV5plainACvgZ",
583        Console = c"$s7SwiftUI28AccessibilityTextContentTypeV7consoleACvgZ",
584        FileSystem = c"$s7SwiftUI28AccessibilityTextContentTypeV10fileSystemACvgZ",
585        Messaging = c"$s7SwiftUI28AccessibilityTextContentTypeV9messagingACvgZ",
586        Narrative = c"$s7SwiftUI28AccessibilityTextContentTypeV9narrativeACvgZ",
587        SourceCode = c"$s7SwiftUI28AccessibilityTextContentTypeV10sourceCodeACvgZ",
588        Spreadsheet = c"$s7SwiftUI28AccessibilityTextContentTypeV11spreadsheetACvgZ",
589        WordProcessing = c"$s7SwiftUI28AccessibilityTextContentTypeV14wordProcessingACvgZ",
590    }
591}
592
593swift_statics! {
594    /// A member of `SwiftUI.AccessibilityDirectTouchOptions`.
595    pub enum DirectTouchOption {
596        SilentOnTouch = c"$s7SwiftUI31AccessibilityDirectTouchOptionsV08silentOnE0ACvgZ",
597        RequiresActivation = c"$s7SwiftUI31AccessibilityDirectTouchOptionsV18requiresActivationACvgZ",
598    }
599}
600
601/// Apply `.accessibility(identifier:)`, the pre-`accessibilityIdentifier` name.
602pub fn accessibility_deprecated_identifier(view: &ViewHandle, id: &str) -> ViewHandle {
603    unsafe {
604        let s = abi::swift_string(id);
605        let w0 = u64::from_le_bytes(s[..8].try_into().expect("string lo"));
606        let w1 = u64::from_le_bytes(s[8..].try_into().expect("string hi"));
607        a11y_modifier(resolve::a11y_deprecated_identifier_fn(), view, &[w0, w1], [0.0; 4])
608    }
609}
610
611/// Apply `.accessibilityIdentifier(_:)`.
612pub fn accessibility_identifier(view: &ViewHandle, id: &str) -> ViewHandle {
613    unsafe {
614        let s = abi::swift_string(id);
615        let w0 = u64::from_le_bytes(s[..8].try_into().expect("string lo"));
616        let w1 = u64::from_le_bytes(s[8..].try_into().expect("string hi"));
617        a11y_modifier(resolve::a11y_identifier_fn(), view, &[w0, w1], [0.0; 4])
618    }
619}
620
621/// Apply `.accessibilitySortPriority(_:)`.
622pub fn accessibility_sort_priority(view: &ViewHandle, priority: f64) -> ViewHandle {
623    unsafe { a11y_modifier(resolve::a11y_sort_priority_fn(), view, &[], [priority, 0.0, 0.0, 0.0]) }
624}
625
626/// Apply `.accessibilityValue(_:isEnabled:)`.
627pub fn accessibility_value(view: &ViewHandle, value: &str, enabled: bool) -> ViewHandle {
628    unsafe {
629        let (k0, k1, k2, k3) = lsk_words(value);
630        a11y_modifier(resolve::a11y_value_fn(), view, &[k0, k1, k2, k3, u64::from(enabled)], [0.0; 4])
631    }
632}
633
634/// Apply `.accessibilityRespondsToUserInteraction(_:)`.
635pub fn accessibility_responds_to_user_interaction(view: &ViewHandle, responds: bool) -> ViewHandle {
636    unsafe {
637        a11y_modifier(resolve::a11y_responds_fn(), view, &[u64::from(responds)], [0.0; 4])
638    }
639}
640
641/// Apply `.accessibilityHint(_:)`.
642pub fn accessibility_hint(view: &ViewHandle, hint: &str) -> ViewHandle {
643    unsafe {
644        let (k0, k1, k2, k3) = lsk_words(hint);
645        a11y_modifier(resolve::a11y_hint_fn(), view, &[k0, k1, k2, k3], [0.0; 4])
646    }
647}
648
649/// Apply `.accessibilityScrollStatus(_:isEnabled:)`.
650pub fn accessibility_scroll_status(view: &ViewHandle, status: &str, enabled: bool) -> ViewHandle {
651    unsafe {
652        let (k0, k1, k2, k3) = lsk_words(status);
653        a11y_modifier(
654            resolve::a11y_scroll_status_fn(),
655            view,
656            &[k0, k1, k2, k3, u64::from(enabled)],
657            [0.0; 4],
658        )
659    }
660}
661
662/// Apply `.accessibilityActivationPoint(_:)`.
663///
664/// A `UnitPoint` is two `CGFloat`s and frozen, so it arrives in d0 and d1
665/// rather than by address.
666pub fn accessibility_activation_point(view: &ViewHandle, x: f64, y: f64) -> ViewHandle {
667    unsafe { a11y_modifier(resolve::a11y_activation_point_fn(), view, &[], [x, y, 0.0, 0.0]) }
668}
669
670/// Apply `.accessibilityDragPoint(_:description:isEnabled:)`.
671pub fn accessibility_drag_point(
672    view: &ViewHandle,
673    x: f64,
674    y: f64,
675    description: &str,
676    enabled: bool,
677) -> ViewHandle {
678    unsafe {
679        let (k0, k1, k2, k3) = lsk_words(description);
680        a11y_modifier(
681            resolve::a11y_drag_point_fn(),
682            view,
683            &[k0, k1, k2, k3, u64::from(enabled)],
684            [x, y, 0.0, 0.0],
685        )
686    }
687}
688
689/// Apply `.accessibilityDropPoint(_:description:isEnabled:)`.
690pub fn accessibility_drop_point(
691    view: &ViewHandle,
692    x: f64,
693    y: f64,
694    description: &str,
695    enabled: bool,
696) -> ViewHandle {
697    unsafe {
698        let (k0, k1, k2, k3) = lsk_words(description);
699        a11y_modifier(
700            resolve::a11y_drop_point_fn(),
701            view,
702            &[k0, k1, k2, k3, u64::from(enabled)],
703            [x, y, 0.0, 0.0],
704        )
705    }
706}
707
708/// Apply `.accessibilityAddTraits(_:)`.
709pub fn accessibility_add_traits(view: &ViewHandle, traits: &[AccessibilityTrait]) -> ViewHandle {
710    unsafe {
711        let v = traits_value(traits);
712        a11y_modifier(resolve::a11y_add_traits_fn(), view, &[v.as_ptr() as u64], [0.0; 4])
713    }
714}
715
716/// Apply `.accessibilityRemoveTraits(_:)`.
717pub fn accessibility_remove_traits(view: &ViewHandle, traits: &[AccessibilityTrait]) -> ViewHandle {
718    unsafe {
719        let v = traits_value(traits);
720        a11y_modifier(resolve::a11y_remove_traits_fn(), view, &[v.as_ptr() as u64], [0.0; 4])
721    }
722}
723
724/// Apply `.accessibilityHeading(_:)`.
725pub fn accessibility_heading(view: &ViewHandle, level: HeadingLevel) -> ViewHandle {
726    unsafe {
727        let meta = abi::metadata0(resolve::a11y_heading_level_ma());
728        let arg = abi::enum_argument(meta, level.tag());
729        a11y_modifier(resolve::a11y_heading_fn(), view, &[arg], [0.0; 4])
730    }
731}
732
733/// Apply `.accessibilityTextContentType(_:)`.
734pub fn accessibility_text_content_type(view: &ViewHandle, kind: TextContentType) -> ViewHandle {
735    unsafe {
736        let meta = abi::metadata0(resolve::a11y_text_content_type_ma());
737        let v = resilient_static(meta, kind.getter());
738        a11y_modifier(
739            resolve::a11y_text_content_type_fn(),
740            view,
741            &[v.as_ptr() as u64],
742            [0.0; 4],
743        )
744    }
745}
746
747/// Apply `.accessibilityInputLabels(_:isEnabled:)`.
748/// Box a `Hashable` value into a `Swift.AnyHashable`.
749///
750/// Forty bytes, written through x8; the value goes in by address with its
751/// metadata and `Hashable` witness beside it. `AnyHashable` is not something
752/// this crate can lay out by hand — the box stores a copy made through the
753/// value's own witness table, which is what `_convertToAnyHashable` is for.
754///
755/// # Safety
756/// `bytes` must be a live value of type `meta`, conforming through `wt`.
757pub(crate) unsafe fn any_hashable(
758    bytes: &[u8],
759    meta: *const c_void,
760    wt: *const c_void,
761) -> Vec<u8> {
762    unsafe {
763        let any_meta = abi::metadata0(resolve::any_hashable_ma());
764        assert!(!any_meta.is_null(), "AnyHashable metadata");
765        let mut out = vec![0u8; abi::type_bytes(any_meta)];
766        core::arch::asm!("blr {f}", f = in(reg) resolve::convert_to_any_hashable(),
767            in("x0") bytes.as_ptr(),
768            in("x1") meta,
769            in("x2") wt,
770            in("x8") out.as_mut_ptr(),
771            lateout("x3") _, lateout("x4") _, lateout("x5") _, lateout("x6") _,
772            lateout("x7") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
773            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
774            lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x20") _,
775            lateout("x21") _, clobber_abi("C"),
776        );
777        out
778    }
779}
780
781/// `.accessibility(selectionIdentifier:)` — which item of a selectable group
782/// this is, for assistive technologies.
783///
784/// Deprecated in Swift, with no modern replacement — `accessibilityIdentifier`
785/// is a different thing (a test hook). Bound because it is the one member of
786/// that family the crate could not otherwise reach.
787///
788/// The argument is `AnyHashable`, which is three words: the boxed value, its
789/// metadata and its `Hashable` witness.
790pub fn accessibility_selection_identifier(
791    view: &ViewHandle,
792    id: super::HashableValue<'_>,
793) -> ViewHandle {
794    unsafe {
795        let (bytes, meta, wt) = id.parts();
796        let any = any_hashable(&bytes, meta, wt);
797        a11y_modifier(resolve::a11y_selection_identifier_fn(), view, &[any.as_ptr() as u64], [0.0; 4])
798    }
799}
800
801pub fn accessibility_input_labels(
802    view: &ViewHandle,
803    labels: &[&str],
804    enabled: bool,
805) -> ViewHandle {
806    unsafe {
807        let meta = resolve::lsk_meta();
808        let stride = abi::type_bytes(meta);
809        let mut bytes = vec![0u8; stride * labels.len()];
810        for (i, l) in labels.iter().enumerate() {
811            let (k0, k1, k2, k3) = lsk_words(l);
812            let at = &mut bytes[i * stride..];
813            at[..8].copy_from_slice(&k0.to_le_bytes());
814            at[8..16].copy_from_slice(&k1.to_le_bytes());
815            at[16..24].copy_from_slice(&k2.to_le_bytes());
816            at[24..32].copy_from_slice(&k3.to_le_bytes());
817        }
818        let array = abi::swift_array(meta, labels.len(), &bytes);
819        a11y_modifier(
820            resolve::a11y_input_labels_fn(),
821            view,
822            &[array, u64::from(enabled)],
823            [0.0; 4],
824        )
825    }
826}
827
828/// Apply `.accessibilityDirectTouch(_:options:)`.
829pub fn accessibility_direct_touch(
830    view: &ViewHandle,
831    enabled: bool,
832    options: &[DirectTouchOption],
833) -> ViewHandle {
834    unsafe {
835        let meta = abi::metadata0(resolve::a11y_direct_touch_options_ma());
836        let mut v = vec![0u8; abi::type_bytes(meta)];
837        // An `OptionSet` is its `rawValue`, so members combine by bitwise or
838        // without going through `formUnion`.
839        for o in options {
840            let one = resilient_static(meta, o.getter());
841            for (dst, src) in v.iter_mut().zip(one.iter()) {
842                *dst |= *src;
843            }
844        }
845        a11y_modifier(
846            resolve::a11y_direct_touch_fn(),
847            view,
848            &[u64::from(enabled), v.as_ptr() as u64],
849            [0.0; 4],
850        )
851    }
852}
853
854/// A `LocalizedStringKey`'s four words.
855pub(crate) unsafe fn lsk_words(s: &str) -> (u64, u64, u64, u64) {
856    unsafe {
857        let ss = abi::swift_string(s);
858        localized_string_key(
859            u64::from_le_bytes(ss[..8].try_into().expect("string lo")),
860            u64::from_le_bytes(ss[8..].try_into().expect("string hi")),
861        )
862    }
863}
864
865/// How prominent a piece of custom accessibility content is.
866///
867/// An imported Objective-C `NS_ENUM`, so it is a plain integer in a register
868/// rather than a Swift enum with a witness table.
869#[derive(Clone, Copy, Debug, PartialEq, Eq)]
870pub enum CustomContentImportance {
871    Default = 0,
872    High = 1,
873}
874
875/// Apply `.accessibilityCustomContent(_:_:importance:)`.
876///
877/// The key is built first, through `AccessibilityCustomContentKey.init(_:id:)`
878/// — four words of `LocalizedStringKey` and two of `String`, returning the key
879/// through x8 because it is resilient. The modifier then takes that key by
880/// address, its own label as another `LocalizedStringKey`, and the importance
881/// in a register.
882pub fn accessibility_custom_content(
883    view: &ViewHandle,
884    key_label: &str,
885    id: &str,
886    value: &str,
887    importance: CustomContentImportance,
888) -> ViewHandle {
889    unsafe {
890        let (kk0, kk1, kk2, kk3) = lsk_words(key_label);
891        let ids = abi::swift_string(id);
892        let i0 = u64::from_le_bytes(ids[..8].try_into().expect("id lo"));
893        let i1 = u64::from_le_bytes(ids[8..].try_into().expect("id hi"));
894        let key_meta = abi::metadata0(resolve::a11y_custom_content_key_ma());
895        let mut key = vec![0u8; abi::type_bytes(key_meta)];
896        core::arch::asm!("blr {f}", f = in(reg) resolve::a11y_custom_content_key_init(),
897            in("x0") kk0, in("x1") kk1, in("x2") kk2, in("x3") kk3,
898            in("x4") i0, in("x5") i1,
899            in("x8") key.as_mut_ptr(), in("x20") key_meta,
900            lateout("x6") _, lateout("x7") _, lateout("x9") _, lateout("x10") _,
901            lateout("x11") _, lateout("x12") _, lateout("x13") _, lateout("x14") _,
902            lateout("x15") _, lateout("x16") _, lateout("x17") _, lateout("lr") _,
903            lateout("x21") _, clobber_abi("C"),
904        );
905        let (v0, v1, v2, v3) = lsk_words(value);
906        let (_, _, size) = A11Y_T.applied_to(view.meta);
907        let r = abi::call_modifier(resolve::a11y_custom_content_fn(), view.value_ptr(), view.meta, view.wt,
908            &[key.as_ptr() as u64, v0, v1, v2, v3, importance as u64],
909            [0.0; 4],
910            size,
911        );
912        wrap_modified(r, &A11Y_T, view)
913    }
914}
915
916/// One end of a `String`, as a `String.Index`.
917///
918/// A `String` is sixteen bytes, which is more than the one register Swift pins
919/// `self` to — so a method on it takes `self` *indirectly*, with the address in
920/// x20. The index itself is one word and comes back in x0.
921unsafe fn string_bound(text: &str, getter: *const c_void) -> u64 {
922    unsafe {
923        let s = abi::swift_string(text);
924        let index: u64;
925        core::arch::asm!("blr {f}", f = in(reg) getter,
926            in("x20") s.as_ptr(),
927            lateout("x0") index,
928            lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
929            lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x8") _,
930            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
931            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
932            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
933        );
934        index
935    }
936}
937
938/// Apply `.accessibilityRotor(_:textRanges:)`.
939///
940/// The ranges are real `Range<String.Index>` values taken from `text` — one
941/// spanning the whole string. `Range` is frozen and its two bounds are stored
942/// back to back, so the array elements are built directly rather than through an
943/// initialiser.
944pub fn accessibility_rotor(view: &ViewHandle, label: &str, text: &str) -> ViewHandle {
945    unsafe {
946        let lower = string_bound(text, resolve::string_start_index());
947        let upper = string_bound(text, resolve::string_end_index());
948        let range = abi::resolve_type_by_mangled_name(mangle::range(&mangle::nested(mangle::STRING, "Index", mangle::Kind::Struct)).as_bytes());
949        assert!(!range.is_null(), "Range<String.Index>");
950        let stride = abi::type_bytes(range);
951        let mut bytes = vec![0u8; stride];
952        bytes[..8].copy_from_slice(&lower.to_le_bytes());
953        bytes[8..16].copy_from_slice(&upper.to_le_bytes());
954        let array = abi::swift_array(range, 1, &bytes);
955        let (k0, k1, k2, k3) = lsk_words(label);
956        let subst = [view.meta, view.wt];
957        let (m, wt, size) = opaque_result_type(resolve::a11y_rotor_oret(), &subst);
958        let r = abi::call_modifier(resolve::a11y_rotor_fn(), view.value_ptr(), view.meta, view.wt,
959            &[k0, k1, k2, k3, array],
960            [0.0; 4],
961            size,
962        );
963        wrap_owned(r, m, wt)
964    }
965}
966
967/// Apply `.accessibilityChartDescriptor(_:)` with a synthesized representable.
968///
969/// No public SwiftUI type conforms to `AXChartDescriptorRepresentable` — the
970/// protocol exists for applications to adopt — so the conformance is built at
971/// run time and hung on `EdgeInsets`, which the reference can name too.
972pub fn accessibility_chart_descriptor(view: &ViewHandle) -> ViewHandle {
973    unsafe {
974        let meta = abi::metadata0(resolve::edge_insets_ma());
975        let wt = crate::conformance::chart_descriptor_representable().witness_table();
976        let value = vec![0u8; abi::type_bytes(meta)];
977        generic1_modifier_ints(
978            resolve::a11y_chart_descriptor_fn(),
979            resolve::a11y_chart_descriptor_oret(),
980            view,
981            meta,
982            wt,
983            &[value.as_ptr() as u64],
984            [0.0; 4],
985        )
986    }
987}