Skip to main content

swiftui_native/views/
generics.rs

1//! Bindings and modifiers generic over a protocol.
2//!
3//! * Generic modifiers: `Hashable`, `Shape`, and focus bindings
4
5use super::*;
6use crate::abi;
7use crate::resolve;
8use core::ffi::c_void;
9use crate::mangle::{self, swift_type};
10
11// ── Bindings and modifiers generic over a protocol ──
12
13//
14// A `Binding` is three registers wide and two-way: SwiftUI reads it to decide
15// what to show and writes back when the user acts, and both ends land in the
16// Rust state it was built from.
17
18/// The concrete types conforming to `SwiftUI.InsettableShape`.
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub enum InsettableShape {
21    Circle,
22    Ellipse,
23    Rectangle,
24    ContainerRelativeShape,
25}
26
27impl InsettableShape {
28    /// The shape's own name, mangled, for spelling a type it appears in.
29    pub(crate) fn type_mangling(self) -> &'static str {
30        match self {
31            Self::Circle => "7SwiftUI6CircleV",
32            Self::Ellipse => "7SwiftUI7EllipseV",
33            Self::Rectangle => "7SwiftUI9RectangleV",
34            Self::ContainerRelativeShape => "7SwiftUI22ContainerRelativeShapeV",
35        }
36    }
37
38    /// The shape's metadata accessor, its `InsettableShape` witness table, and
39    /// its `init()`.
40    pub(crate) fn parts(self) -> (*const c_void, *const c_void, *const c_void) {
41        match self {
42            Self::Circle => (
43                resolve::circle_ma(),
44                resolve::circle_insettable_wt(),
45                resolve::circle_init(),
46            ),
47            Self::Ellipse => (
48                resolve::ellipse_ma(),
49                resolve::ellipse_insettable_wt(),
50                resolve::ellipse_init(),
51            ),
52            Self::Rectangle => (
53                resolve::rectangle_ma(),
54                resolve::rectangle_insettable_wt(),
55                resolve::rectangle_init(),
56            ),
57            Self::ContainerRelativeShape => (
58                resolve::container_relative_shape_ma(),
59                resolve::container_relative_shape_insettable_wt(),
60                resolve::container_relative_shape_init(),
61            ),
62        }
63    }
64}
65
66/// Apply `.findNavigator(isPresented:)`.
67///
68/// The binding is two-way: SwiftUI reads it to decide whether to show the find
69/// bar and writes back when the user dismisses it, so `state` sees both.
70pub fn find_navigator(view: &ViewHandle, state: &BoundState<bool>) -> ViewHandle {
71    unsafe {
72        let mut b = bool_binding(state);
73        let w = binding_words(&mut b);
74        opaque_modifier(
75            resolve::find_navigator_fn(),
76            resolve::find_navigator_oret(),
77            view,
78            &w,
79            [0.0; 4],
80        )
81    }
82}
83
84/// Apply `.dialogSuppressionToggle(isSuppressed:)`.
85pub fn dialog_suppression_toggle(view: &ViewHandle, state: &BoundState<bool>) -> ViewHandle {
86    unsafe {
87        let mut b = bool_binding(state);
88        let w = binding_words(&mut b);
89        opaque_modifier(
90            resolve::dialog_suppression_toggle_fn(),
91            resolve::dialog_suppression_toggle_oret(),
92            view,
93            &w,
94            [0.0; 4],
95        )
96    }
97}
98
99/// Apply `.dialogSuppressionToggle(_:isSuppressed:)` with a label.
100///
101/// Seven integer arguments — four for the key, three for the binding — so
102/// `Self.Type` and the witness table travel on the stack rather than in
103/// registers.
104pub fn dialog_suppression_toggle_labelled(
105    view: &ViewHandle,
106    label: &str,
107    state: &BoundState<bool>,
108) -> ViewHandle {
109    unsafe {
110        let ss = abi::swift_string(label);
111        let k = localized_string_key(
112            u64::from_le_bytes(ss[..8].try_into().expect("lo")),
113            u64::from_le_bytes(ss[8..].try_into().expect("hi")),
114        );
115        let mut b = bool_binding(state);
116        let w = binding_words(&mut b);
117        opaque_modifier(
118            resolve::dialog_suppression_toggle_labelled_fn(),
119            resolve::dialog_suppression_toggle_labelled_oret(),
120            view,
121            &[k.0, k.1, k.2, k.3, w[0], w[1], w[2]],
122            [0.0; 4],
123        )
124    }
125}
126
127/// Apply `.highPriorityGesture(_:including:)` with a `DragGesture`.
128///
129/// Generic over `Gesture`, so the call carries the gesture's metadata and its
130/// conformance alongside `Self`'s — the same shape as a style modifier, with a
131/// `GestureMask` on the end.
132pub fn high_priority_gesture(view: &ViewHandle, minimum_distance: f64, action: impl Fn() + 'static) -> ViewHandle {
133    unsafe {
134        gesture_variant(
135            resolve::high_priority_gesture_fn(),
136            resolve::high_priority_gesture_oret(),
137            view,
138            minimum_distance,
139            action,
140        )
141    }
142}
143
144/// Apply `.simultaneousGesture(_:including:)` with a `DragGesture`.
145///
146/// Generic over `Gesture`, so the call carries the gesture's metadata and its
147/// conformance alongside `Self`'s — the same shape as a style modifier, with a
148/// `GestureMask` on the end.
149pub fn simultaneous_gesture(view: &ViewHandle, minimum_distance: f64, action: impl Fn() + 'static) -> ViewHandle {
150    unsafe {
151        gesture_variant(
152            resolve::simultaneous_gesture_fn(),
153            resolve::simultaneous_gesture_oret(),
154            view,
155            minimum_distance,
156            action,
157        )
158    }
159}
160
161/// Apply `.containerShape(_:)`.
162///
163/// Three overloads share this name, differing only by extra constraints; this
164/// is the one taking a plain `InsettableShape`. A shape that also conforms to
165/// `RoundedRectangularShape` — `Circle`, `Rectangle`, `Capsule` — resolves to a
166/// different overload in Swift source than the symbol bound here, and so
167/// produces a different modifier.
168pub fn container_shape(view: &ViewHandle, shape: InsettableShape) -> ViewHandle {
169    unsafe {
170        let (meta, wt, init) = shape.parts();
171        style_modifier(
172            resolve::container_shape_fn(),
173            resolve::container_shape_oret(),
174            view,
175            meta,
176            wt,
177            init,
178        )
179    }
180}
181
182// ── Generic modifiers: `Hashable`, `Shape`, and focus bindings ──
183
184/// Apply a modifier with one generic parameter beyond `Self`.
185///
186/// [`opaque_modifier`] cannot express these. It appends `Self.Type` and the
187/// `Self: View` witness table to the arguments, which is the whole tail for an
188/// ordinary modifier; a generic one takes a second metadata argument and a
189/// second witness table, and the value arguments come first. So the sequence is
190/// value, then any further arguments, then both types, then both conformances.
191///
192/// # Safety
193/// `arg` must be a valid value of the type `arg_meta` describes, and `arg_wt`
194/// its conformance to whatever protocol the modifier constrains the parameter
195/// to.
196pub(crate) unsafe fn generic1_modifier(
197    func: *const c_void,
198    oret: *const c_void,
199    view: &ViewHandle,
200    arg_meta: *const c_void,
201    arg_wt: *const c_void,
202    arg: &[u8],
203    extra: &[u64],
204) -> ViewHandle {
205    unsafe {
206        let subst = [view.meta, arg_meta, view.wt, arg_wt];
207        let (meta, wt, size) = opaque_result_type(oret, &subst);
208        let mut ints = Vec::with_capacity(5 + extra.len());
209        ints.push(arg.as_ptr() as u64);
210        ints.extend_from_slice(extra);
211        ints.push(view.meta as u64);
212        ints.push(arg_meta as u64);
213        ints.push(view.wt as u64);
214        ints.push(arg_wt as u64);
215        let r = abi::call_modifier_generic(func, view.value_ptr(), &ints, [0.0; 4], size);
216        wrap_owned(r, meta, wt)
217    }
218}
219
220/// A value to pass where SwiftUI asks for `some Hashable`.
221///
222/// `id` and `tag` are generic over `Hashable` rather than over any SwiftUI
223/// protocol, so the conformance comes from the standard library and the choice
224/// of concrete type is the caller's.
225#[derive(Clone, Copy, Debug, PartialEq)]
226pub enum HashableValue<'a> {
227    Str(&'a str),
228    Int(i64),
229    Bool(bool),
230    Double(f64),
231}
232
233impl HashableValue<'_> {
234    /// The value's bytes, its metadata, and its `Hashable` witness table.
235    pub(crate) unsafe fn parts(self) -> (Vec<u8>, *const c_void, *const c_void) {
236        unsafe {
237            match self {
238                Self::Str(s) => (
239                    abi::swift_string(s).to_vec(),
240                    resolve::string_meta(),
241                    resolve::string_hashable_wt(),
242                ),
243                Self::Int(i) => {
244                    (i.to_le_bytes().to_vec(), resolve::int_meta(), resolve::int_hashable_wt())
245                }
246                Self::Bool(b) => {
247                    (vec![u8::from(b)], resolve::bool_meta(), resolve::bool_hashable_wt())
248                }
249                Self::Double(d) => {
250                    (d.to_le_bytes().to_vec(), resolve::double_meta(), resolve::double_hashable_wt())
251                }
252            }
253        }
254    }
255}
256
257/// Anything that can stand where SwiftUI wants `some Hashable`.
258///
259/// So that `.id(7)` and `.id("row")` are what a caller writes, rather than
260/// `.id(HashableValue::Int(7))`. The enum stays as the carrier — it is what
261/// crosses into the unsafe code — and this is the conversion into it.
262///
263/// Implemented per Rust type rather than blanket over `Hash`: the value has to
264/// cross into Swift, so it needs a Swift type with a `Hashable` conformance to
265/// cross *as*, and that mapping is a decision per type rather than something
266/// derivable.
267pub trait IntoHashable<'a> {
268    /// This value as the carrier.
269    fn into_hashable(self) -> HashableValue<'a>;
270}
271
272impl<'a> IntoHashable<'a> for HashableValue<'a> {
273    fn into_hashable(self) -> HashableValue<'a> {
274        self
275    }
276}
277
278impl<'a> IntoHashable<'a> for &'a str {
279    fn into_hashable(self) -> HashableValue<'a> {
280        HashableValue::Str(self)
281    }
282}
283
284impl<'a> IntoHashable<'a> for &'a String {
285    fn into_hashable(self) -> HashableValue<'a> {
286        HashableValue::Str(self)
287    }
288}
289
290impl IntoHashable<'_> for i64 {
291    fn into_hashable(self) -> HashableValue<'static> {
292        HashableValue::Int(self)
293    }
294}
295
296impl IntoHashable<'_> for i32 {
297    fn into_hashable(self) -> HashableValue<'static> {
298        // Swift's `Int` is 64-bit everywhere this runs, so a narrower Rust
299        // integer widens rather than getting a `Hashable` of its own — an
300        // `Int32` id would hash differently from the `Int` beside it.
301        HashableValue::Int(i64::from(self))
302    }
303}
304
305impl IntoHashable<'_> for usize {
306    fn into_hashable(self) -> HashableValue<'static> {
307        HashableValue::Int(self as i64)
308    }
309}
310
311impl IntoHashable<'_> for bool {
312    fn into_hashable(self) -> HashableValue<'static> {
313        HashableValue::Bool(self)
314    }
315}
316
317impl IntoHashable<'_> for f64 {
318    fn into_hashable(self) -> HashableValue<'static> {
319        HashableValue::Double(self)
320    }
321}
322
323/// Apply `.id(_:)`.
324pub fn id(view: &ViewHandle, value: HashableValue<'_>) -> ViewHandle {
325    unsafe {
326        let (bytes, meta, wt) = value.parts();
327        generic1_modifier(resolve::id_fn(), resolve::id_oret(), view, meta, wt, &bytes, &[])
328    }
329}
330
331/// Apply a `ViewModifier` *value* to a view.
332///
333/// The general case, and the way out of a corner: SwiftUI's convenience
334/// spellings are increasingly `@_alwaysEmitIntoClient`, which means the
335/// compiler inlines them into the caller and the framework exports no symbol to
336/// call. `View.modifier(_:)` is exported, is generic over the modifier with no
337/// constraint at all, and returns `ModifiedContent<Self, M>` concretely — so
338/// building `M`'s value and handing it over reaches every one of them.
339///
340/// # Safety
341/// `bytes` must be a valid value of the type `modifier_meta` describes, and
342/// that type must conform to `ViewModifier`.
343pub unsafe fn apply_modifier(
344    view: &ViewHandle,
345    modifier_meta: *const c_void,
346    bytes: &[u8],
347) -> ViewHandle {
348    unsafe {
349        // `ModifiedContent<Self, Modifier>` — `Self` being the receiver, not
350        // `AnyView`. Composing against `AnyView` while handing the call a
351        // concrete receiver described a result of the wrong size and shape, and
352        // the callee wrote through it.
353        let content = abi::generic_metadata2(
354            resolve::modified_content_ma(),
355            view.meta,
356            modifier_meta,
357        );
358        let wt = abi::get_view_wt(content);
359        assert!(!wt.is_null(), "{} !: View", crate::diag::type_name(content));
360        let ints = [
361            bytes.as_ptr() as u64,
362            view.meta as u64,
363            modifier_meta as u64,
364            view.wt as u64,
365        ];
366        let r = abi::call_modifier_generic(resolve::view_modifier_fn(), view.value_ptr(),
367            &ints,
368            [0.0; 4],
369            abi::type_bytes(content),
370        );
371        wrap_owned(r, content, wt)
372    }
373}
374
375/// Apply `.tag(_:)`.
376///
377/// Not through the exported `View.tag` symbol, which is the pre-macOS 26
378/// spelling and still builds `_TraitWritingModifier<TagValueTraitKey<V>>`. The
379/// modifier Swift source produces today is `_TagTraitWritingModifier<V>`,
380/// written by an `@_alwaysEmitIntoClient` function that exports nothing — so
381/// the value is assembled here and applied with [`apply_modifier`]. The struct
382/// is `@frozen`, which is what makes laying its two fields out by hand sound.
383pub fn tag(view: &ViewHandle, value: HashableValue<'_>) -> ViewHandle {
384    unsafe {
385        let (bytes, meta, wt) = value.parts();
386        let modifier = abi::generic_metadata2(resolve::tag_trait_writing_modifier_ma(), meta, wt);
387        let size = abi::type_size(modifier);
388        let at = abi::type_size(meta);
389        assert_eq!(size, at + 1, "_TagTraitWritingModifier is not tag + Bool");
390        let mut v = vec![0u8; abi::type_bytes(modifier)];
391        v[..bytes.len()].copy_from_slice(&bytes);
392        // `includeOptional` follows the tag, and defaults to true.
393        v[at] = 1;
394        apply_modifier(view, modifier, &v)
395    }
396}
397
398impl InsettableShape {
399    /// The same shapes seen as plain `Shape`s, for modifiers that ask only for
400    /// that. A conformance is per protocol, not per type, so `contentShape`
401    /// needs a different witness table from `containerShape` for the very same
402    /// `Circle`.
403    pub(crate) fn shape_parts(self) -> (*const c_void, *const c_void, *const c_void) {
404        match self {
405            Self::Circle => (resolve::circle_ma(), resolve::circle_shape_wt(), resolve::circle_init()),
406            Self::Ellipse => (resolve::ellipse_ma(), resolve::ellipse_shape_wt(), resolve::ellipse_init()),
407            Self::Rectangle => (resolve::rectangle_ma(), resolve::rectangle_shape_wt(), resolve::rectangle_init()),
408            Self::ContainerRelativeShape => (
409                resolve::container_relative_shape_ma(),
410                resolve::container_relative_shape_shape_wt(),
411                resolve::container_relative_shape_init(),
412            ),
413        }
414    }
415}
416
417/// Apply `.contentShape(_:eoFill:)`.
418pub fn content_shape(view: &ViewHandle, shape: InsettableShape, eo_fill: bool) -> ViewHandle {
419    unsafe {
420        let (ma, wt, init) = shape.shape_parts();
421        let meta = abi::metadata0(ma);
422        let mut value = vec![0u8; abi::type_bytes(meta)];
423        abi::call_out(init, [0; 8], [0.0; 4], meta as u64, value.as_mut_ptr());
424        generic1_modifier(
425            resolve::content_shape_fn(),
426            resolve::content_shape_oret(),
427            view,
428            meta,
429            wt,
430            &value,
431            &[u64::from(eo_fill)],
432        )
433    }
434}
435
436/// Build a `FocusState<Bool>.Binding` by projecting a fresh `FocusState`.
437///
438/// `FocusState` is `@frozen`, so both it and its nested `Binding` are three
439/// words that travel in registers rather than by address. The projection is a
440/// method on a generic struct, which means the caller supplies `Value`'s
441/// metadata and its `Hashable` conformance even though `Value` is pinned to
442/// `Bool` here.
443///
444/// A binding made this way is not attached to a view graph, so nothing will
445/// ever set it — which is exactly what `@FocusState var` produces before the
446/// view it belongs to is installed, and what the modifier stores either way.
447pub(crate) unsafe fn focus_binding(state_type: &[u8], init: *const c_void, projected: *const c_void) -> [u64; 3] {
448    unsafe {
449        let state_meta = abi::resolve_type_by_mangled_name(state_type);
450        assert!(!state_meta.is_null(), "focus state metadata");
451        // `init()` is spelled `<Value where Value == Bool>`, which still makes
452        // it a generic function: `FocusState<Value>` is address-only in its own
453        // body, so the new value is written through x8 rather than returned in
454        // registers. Reading x0-x2 instead happens to yield zeroes for
455        // `FocusState`, which is the right answer for the wrong reason and the
456        // wrong one for `AccessibilityFocusState`.
457        let mut this = [0u64; 3];
458        core::arch::asm!("blr {f}", f = in(reg) init,
459            in("x8") this.as_mut_ptr(), in("x20") state_meta,
460            lateout("x0") _, lateout("x1") _, lateout("x2") _,
461            lateout("x3") _, lateout("x4") _, lateout("x5") _, lateout("x6") _,
462            lateout("x7") _, lateout("x9") _, lateout("x10") _,
463            lateout("x11") _, lateout("x12") _, lateout("x13") _, lateout("x14") _,
464            lateout("x15") _, lateout("x16") _, lateout("x17") _, lateout("lr") _,
465            lateout("x21") _, clobber_abi("C"),
466        );
467        // `projectedValue` is generic in `Value`, so inside it both
468        // `FocusState<Value>` and the `Binding` it returns are address-only:
469        // the receiver arrives by pointer in x20 and the result is written
470        // through x8. Only the *caller* of `.focused(_:)` knows `Value == Bool`
471        // and can pass the binding's three words in registers.
472        //
473        // The metadata in x0 is the bound `FocusState<Bool>`, not `Bool`. That
474        // is the one place this getter departs from the usual rule that a
475        // generic function receives its type *parameters*: passing `Bool` there
476        // faults inside the first value-witness call.
477        let mut out = vec![0u64; 3];
478        core::arch::asm!("blr {f}", f = in(reg) projected,
479            in("x0") state_meta, in("x1") resolve::bool_hashable_wt(),
480            in("x8") out.as_mut_ptr(), in("x20") this.as_mut_ptr(),
481            lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
482            lateout("x6") _, lateout("x7") _, lateout("x9") _,
483            lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
484            lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
485            lateout("lr") _, lateout("x21") _, clobber_abi("C"),
486        );
487        [out[0], out[1], out[2]]
488    }
489}
490
491/// Apply `.focused(_:)`.
492pub fn focused(view: &ViewHandle) -> ViewHandle {
493    unsafe {
494        let b = focus_binding(
495            mangle::bound(&swift_type!(SwiftUI.FocusState), &[mangle::BOOL]).as_bytes(),
496            resolve::focus_state_bool_init(),
497            resolve::focus_state_projected(),
498        );
499        opaque_modifier(resolve::focused_fn(), resolve::focused_oret(), view, &b, [0.0; 4])
500    }
501}
502
503/// Apply `.focused(_:)` with a real, installed `@FocusState`.
504///
505/// [`focused`] applies the same modifier with a `FocusState` built on the spot,
506/// which is what SwiftUI stores either way but leaves nothing to read or move.
507/// This takes the projection of a property SwiftUI has installed, so
508/// [`crate::state::ComposedView::focus`] reports whether the field has focus
509/// and [`crate::state::ComposedView::set_focus`] puts it there.
510pub fn focused_on(view: &ViewHandle, projection: [u64; 3]) -> ViewHandle {
511    unsafe {
512        opaque_modifier(
513            resolve::focused_fn(),
514            resolve::focused_oret(),
515            view,
516            &projection,
517            [0.0; 4],
518        )
519    }
520}
521
522/// Apply `.searchFocused(_:)`.
523pub fn search_focused(view: &ViewHandle) -> ViewHandle {
524    unsafe {
525        let b = focus_binding(
526            mangle::bound(&swift_type!(SwiftUI.FocusState), &[mangle::BOOL]).as_bytes(),
527            resolve::focus_state_bool_init(),
528            resolve::focus_state_projected(),
529        );
530        opaque_modifier(
531            resolve::search_focused_fn(),
532            resolve::search_focused_oret(),
533            view,
534            &b,
535            [0.0; 4],
536        )
537    }
538}
539
540/// Apply `.accessibilityFocused(_:)`.
541pub fn accessibility_focused(view: &ViewHandle) -> ViewHandle {
542    unsafe {
543        let b = focus_binding(
544            mangle::bound(&swift_type!(SwiftUI.AccessibilityFocusState), &[mangle::BOOL]).as_bytes(),
545            resolve::a11y_focus_state_bool_init(),
546            resolve::a11y_focus_state_projected(),
547        );
548        opaque_modifier(
549            resolve::a11y_focused_fn(),
550            resolve::a11y_focused_oret(),
551            view,
552            &b,
553            [0.0; 4],
554        )
555    }
556}
557
558/// Apply `.coordinateSpace(name:)`.
559pub fn coordinate_space(view: &ViewHandle, name: HashableValue<'_>) -> ViewHandle {
560    unsafe {
561        let (bytes, meta, wt) = name.parts();
562        generic1_modifier(
563            resolve::coordinate_space_name_fn(),
564            resolve::coordinate_space_name_oret(),
565            view,
566            meta,
567            wt,
568            &bytes,
569            &[],
570        )
571    }
572}
573
574/// A `Hashable` type, named rather than carried.
575///
576/// The metatype-keyed modifiers take `A.Type` rather than an `A`, so they need
577/// the metadata and the conformance but no value.
578#[derive(Clone, Copy, Debug, PartialEq, Eq)]
579pub enum HashableKind {
580    Str,
581    Int,
582}
583
584/// Apply `.scrollPosition(id:anchor:)`.
585///
586/// The anchor is an optional `UnitPoint`, which travels as three integer
587/// registers rather than two float ones and a tag.
588pub fn scroll_position_id(
589    view: &ViewHandle,
590    state: &BoundState<String>,
591    anchor: Option<(f64, f64)>,
592) -> ViewHandle {
593    unsafe {
594        let g = state.clone();
595        let st = state.clone();
596        let value_meta = abi::resolve_type_by_mangled_name(mangle::optional(mangle::STRING).as_bytes());
597        let binding = binding_get_set(
598            value_meta,
599            mangle::bound(&swift_type!(SwiftUI.Binding), &[&mangle::optional(mangle::STRING)]).as_bytes(),
600            Box::new(move |out| {
601                let s = abi::swift_string(&g.get());
602                core::ptr::copy_nonoverlapping(s.as_ptr(), out, 16);
603            }),
604            Box::new(move |v| {
605                let mut raw = [0u8; 16];
606                core::ptr::copy_nonoverlapping(v, raw.as_mut_ptr(), 16);
607                st.set(abi::read_swift_string(raw));
608            }),
609        );
610        // `Binding<A?>` with `A` generic is address-only, so the binding goes
611        // by pointer rather than in the three registers a concrete one uses.
612        let mut ints = vec![binding.as_ptr() as u64];
613        ints.extend(opt_unit_point_words(anchor));
614        generic1_modifier_ints(
615            resolve::scroll_position_id_fn(),
616            resolve::scroll_position_id_oret(),
617            view,
618            resolve::string_meta(),
619            resolve::string_hashable_wt(),
620            &ints,
621            [0.0; 4],
622        )
623    }
624}
625
626/// Apply `.preference(key:value:)` with a runtime-synthesized key.
627///
628/// The key type carries a `PreferenceKey` conformance built by
629/// [`crate::conformance`] at run time, and its `Value` is `Int`. No Swift
630/// source declares it, which is the whole point: this is the one family of
631/// modifiers that expects the *client* to supply a conformance.
632pub fn preference_int(view: &ViewHandle, value: i64) -> ViewHandle {
633    unsafe {
634        let wt = crate::conformance::preference_key().witness_table();
635        let meta = resolve::spacer_meta();
636        // `key: K.Type` is the metadata itself. `value: K.Value` is an
637        // *associated* type, which the callee only knows abstractly, so it
638        // travels by address however small the concrete type turns out to be.
639        let mut boxed = value;
640        generic1_modifier_ints(
641            resolve::preference_fn(),
642            resolve::preference_oret(),
643            view,
644            meta,
645            wt,
646            &[meta as u64, &mut boxed as *mut i64 as u64],
647            [0.0; 4],
648        )
649    }
650}
651
652/// Apply a modifier with one generic parameter carrying *two* conformances.
653unsafe fn generic2_modifier_ints(
654    func: *const c_void,
655    oret: *const c_void,
656    view: &ViewHandle,
657    arg_meta: *const c_void,
658    wt_a: *const c_void,
659    wt_b: *const c_void,
660    leading: &[u64],
661) -> ViewHandle {
662    unsafe {
663        let subst = [view.meta, arg_meta, view.wt, wt_a, wt_b];
664        let (meta, wt, size) = opaque_result_type(oret, &subst);
665        let mut ints = leading.to_vec();
666        ints.push(view.meta as u64);
667        ints.push(arg_meta as u64);
668        ints.push(view.wt as u64);
669        ints.push(wt_a as u64);
670        ints.push(wt_b as u64);
671        let r = abi::call_modifier_generic(func, view.value_ptr(), &ints, [0.0; 4], size);
672        wrap_owned(r, meta, wt)
673    }
674}
675
676/// Where [`on_preference_change_int`] records what SwiftUI handed it.
677pub(crate) static OBSERVED_PREFERENCE: std::sync::atomic::AtomicI64 =
678    std::sync::atomic::AtomicI64::new(i64::MIN);
679
680pub(crate) unsafe extern "C" fn record_preference(
681    a0: u64,
682    _a1: u64,
683    _a2: u64,
684    _payload: *mut c_void,
685) {
686    // `K.Value` is an associated type, so it arrives by address.
687    let v = unsafe { *(a0 as *const i64) };
688    OBSERVED_PREFERENCE.store(v, std::sync::atomic::Ordering::SeqCst);
689}
690
691/// Apply `.onPreferenceChange(_:perform:)`, recording the value.
692///
693/// The value is read back through [`observed_preference`]. `K.Value` must also
694/// be `Equatable` here, so the call carries two witness tables for the one
695/// generic parameter.
696pub fn on_preference_change_int(view: &ViewHandle) -> ViewHandle {
697    unsafe {
698        let wt = crate::conformance::preference_key().witness_table();
699        let meta = resolve::spacer_meta();
700        let c = wide_words(record_preference, core::ptr::null_mut());
701        generic2_modifier_ints(
702            resolve::on_preference_change_fn(),
703            resolve::on_preference_change_oret(),
704            view,
705            meta,
706            wt,
707            resolve::int_equatable_wt(),
708            &[meta as u64, c[0], c[1]],
709        )
710    }
711}
712
713/// The last preference value `onPreferenceChange` reported, if any.
714pub fn observed_preference() -> Option<i64> {
715    let v = OBSERVED_PREFERENCE.load(std::sync::atomic::Ordering::SeqCst);
716    (v != i64::MIN).then_some(v)
717}
718
719pub(crate) unsafe extern "C" fn double_preference(
720    a0: u64,
721    _a1: u64,
722    _a2: u64,
723    _payload: *mut c_void,
724) {
725    // `inout K.Value`, so the pointer is writable.
726    unsafe {
727        let p = a0 as *mut i64;
728        *p *= 2;
729    }
730}
731
732/// Apply `.transformPreference(_:_:)`, doubling the value in place.
733pub fn transform_preference_double(view: &ViewHandle) -> ViewHandle {
734    unsafe {
735        let wt = crate::conformance::preference_key().witness_table();
736        let meta = resolve::spacer_meta();
737        let c = wide_words(double_preference, core::ptr::null_mut());
738        generic1_modifier_ints(
739            resolve::transform_preference_fn(),
740            resolve::transform_preference_oret(),
741            view,
742            meta,
743            wt,
744            &[meta as u64, c[0], c[1]],
745            [0.0; 4],
746        )
747    }
748}
749
750
751/// `String`'s `Transferable` conformance, found at run time.
752///
753/// No synthesis needed: `String`, `URL`, `Data` and `Image` all conform
754/// already, and the runtime hands the witness table over on request. What made
755/// this look unreachable before was the protocol descriptor's mangled name —
756/// `Transferable` inside `CoreTransferable` is word-substituted, so it is
757/// `$s16CoreTransferable0B0Mp` and not the spelled-out form.
758unsafe fn string_transferable() -> (*const c_void, *const c_void) {
759    unsafe {
760        let meta = resolve::string_meta();
761        let wt = abi::get_view_wt_for_proto(meta, resolve::transferable_proto());
762        assert!(!wt.is_null(), "String: Transferable");
763        (meta, wt)
764    }
765}
766
767/// A Swift `[String]`, built from Rust strings and kept alive.
768pub(crate) unsafe fn swift_string_array(items: &[&str]) -> u64 {
769    unsafe {
770        let meta = resolve::string_meta();
771        let stride = abi::type_bytes(meta);
772        let mut bytes = vec![0u8; stride * items.len()];
773        for (i, s) in items.iter().enumerate() {
774            bytes[i * stride..i * stride + 16].copy_from_slice(&abi::swift_string(s));
775        }
776        abi::swift_array(meta, items.len(), &bytes)
777    }
778}
779
780/// Read a Swift `[String]` back into Rust.
781pub(crate) unsafe fn read_string_array(array: u64) -> Vec<String> {
782    unsafe {
783        if array == 0 {
784            return Vec::new();
785        }
786        let stride = abi::type_bytes(resolve::string_meta());
787        let (base, count) = abi::array_parts(array);
788        (0..count)
789            .map(|i| {
790                let mut raw = [0u8; 16];
791                core::ptr::copy_nonoverlapping(base.add(i * stride), raw.as_mut_ptr(), 16);
792                abi::read_swift_string(raw)
793            })
794            .collect()
795    }
796}
797
798/// Whether a drop of these strings should be accepted.
799pub(crate) type StringsDecide = Box<dyn Fn(&[String]) -> bool>;
800
801/// What a strings drop handler is told, with no answer expected.
802pub(crate) type StringsObserve = Box<dyn Fn(&[String])>;
803
804/// A payload the closures below carry: a prebuilt array, or a Rust callback.
805pub(crate) struct StringsPayload {
806    pub(crate) array: u64,
807    pub(crate) decide: Option<StringsDecide>,
808    pub(crate) observe: Option<StringsObserve>,
809}
810
811/// `() -> [String]` — hand back the array this closure was built with.
812pub(crate) unsafe extern "C" fn emit_strings(payload: *mut c_void, _arg: u64) -> u64 {
813    unsafe {
814        let p = &*(payload as *const StringsPayload);
815        // The caller takes ownership of what a closure returns.
816        swift_runtime_sys::RuntimeRaw::swift_retain(p.array as *mut c_void);
817        p.array
818    }
819}
820
821/// `([String]) -> Bool` — decode the array and ask the Rust callback.
822pub(crate) unsafe extern "C" fn decide_on_strings(payload: *mut c_void, arg: u64) -> u64 {
823    unsafe {
824        let p = &*(payload as *const StringsPayload);
825        let items = read_string_array(arg);
826        u64::from(p.decide.as_ref().map(|f| f(&items)).unwrap_or(false))
827    }
828}
829
830/// `([String]) -> [String]` — let the callback see them, return them unchanged.
831pub(crate) unsafe extern "C" fn observe_strings(payload: *mut c_void, arg: u64) -> u64 {
832    unsafe {
833        let p = &*(payload as *const StringsPayload);
834        if let Some(f) = p.observe.as_ref() {
835            f(&read_string_array(arg));
836        }
837        swift_runtime_sys::RuntimeRaw::swift_retain(arg as *mut c_void);
838        arg
839    }
840}
841
842fn strings_payload(array: u64) -> *mut c_void {
843    Box::leak(Box::new(StringsPayload { array, decide: None, observe: None }))
844        as *mut StringsPayload as *mut c_void
845}
846
847fn callback_payload(
848    decide: Option<StringsDecide>,
849    observe: Option<StringsObserve>,
850) -> *mut c_void {
851    Box::leak(Box::new(StringsPayload { array: 0, decide, observe }))
852        as *mut StringsPayload as *mut c_void
853}
854
855/// Apply `.copyable(_:)` with the strings to offer.
856pub fn copyable(view: &ViewHandle, items: &[&str]) -> ViewHandle {
857    unsafe {
858        let (meta, wt) = string_transferable();
859        let c = word_closure(emit_strings, strings_payload(swift_string_array(items)));
860        generic1_modifier_ints(
861            resolve::copyable_fn(),
862            resolve::copyable_oret(),
863            view,
864            meta,
865            wt,
866            &[c[0], c[1]],
867            [0.0; 4],
868        )
869    }
870}
871
872/// Apply `.cuttable(for:action:)` with the strings to offer.
873pub fn cuttable(view: &ViewHandle, items: &[&str]) -> ViewHandle {
874    unsafe {
875        let (meta, wt) = string_transferable();
876        let c = word_closure(emit_strings, strings_payload(swift_string_array(items)));
877        generic1_modifier_ints(
878            resolve::cuttable_fn(),
879            resolve::cuttable_oret(),
880            view,
881            meta,
882            wt,
883            &[meta as u64, c[0], c[1]],
884            [0.0; 4],
885        )
886    }
887}
888
889/// Apply `.exportableToServices(_:)` with the strings to export.
890pub fn exportable_to_services(view: &ViewHandle, items: &[&str]) -> ViewHandle {
891    unsafe {
892        let (meta, wt) = string_transferable();
893        let c = word_closure(emit_strings, strings_payload(swift_string_array(items)));
894        generic1_modifier_ints(
895            resolve::exportable_to_services_fn(),
896            resolve::exportable_to_services_oret(),
897            view,
898            meta,
899            wt,
900            &[c[0], c[1]],
901            [0.0; 4],
902        )
903    }
904}
905
906/// Apply `.importableFromServices(for:action:)`.
907///
908/// `accept` sees the imported strings and answers whether it took them.
909pub fn importable_from_services(view: &ViewHandle, accept: impl Fn(&[String]) -> bool + 'static) -> ViewHandle {
910    unsafe {
911        let (meta, wt) = string_transferable();
912        let c = word_closure(decide_on_strings, callback_payload(Some(Box::new(accept)), None));
913        generic1_modifier_ints(
914            resolve::importable_from_services_fn(),
915            resolve::importable_from_services_oret(),
916            view,
917            meta,
918            wt,
919            &[meta as u64, c[0], c[1]],
920            [0.0; 4],
921        )
922    }
923}
924
925/// Apply `.dropDestination(for:action:isTargeted:)`.
926///
927/// `accept` sees the dropped strings and answers whether it took them;
928/// `targeted` is told when the drop enters and leaves.
929pub fn drop_destination(
930    view: &ViewHandle,
931    accept: impl Fn(&[String]) -> bool + 'static,
932    targeted: impl Fn(bool) + 'static,
933) -> ViewHandle {
934    unsafe {
935        let (meta, wt) = string_transferable();
936        // `([A], CGPoint) -> Bool`: the array is in x0 and the point in d0/d1,
937        // and the answer comes back in x0 — so the one-word closure serves.
938        let action = word_closure(decide_on_strings, callback_payload(Some(Box::new(accept)), None));
939        let is_targeted = bool_words(targeted);
940        generic1_modifier_ints(
941            resolve::drop_destination_fn(),
942            resolve::drop_destination_oret(),
943            view,
944            meta,
945            wt,
946            &[meta as u64, action[0], action[1], is_targeted[0], is_targeted[1]],
947            [0.0; 4],
948        )
949    }
950}
951
952/// Apply `.pasteDestination(for:action:validator:)`.
953///
954/// `paste` sees what was pasted; the validator passes every item through.
955pub fn paste_destination(view: &ViewHandle, paste: impl Fn(&[String]) + 'static) -> ViewHandle {
956    unsafe {
957        let (meta, wt) = string_transferable();
958        let action = wide_words(invoke_string_array, callback_payload(None, Some(Box::new(paste))));
959        let validator = word_closure(observe_strings, callback_payload(None, None));
960        generic1_modifier_ints(
961            resolve::paste_destination_fn(),
962            resolve::paste_destination_oret(),
963            view,
964            meta,
965            wt,
966            &[meta as u64, action[0], action[1], validator[0], validator[1]],
967            [0.0; 4],
968        )
969    }
970}
971
972/// `([String]) -> ()` — decode and hand to the Rust callback.
973pub(crate) unsafe extern "C" fn invoke_string_array(
974    a0: u64,
975    _a1: u64,
976    _a2: u64,
977    payload: *mut c_void,
978) {
979    unsafe {
980        let p = &*(payload as *const StringsPayload);
981        if let Some(f) = p.observe.as_ref() {
982            f(&read_string_array(a0));
983        }
984    }
985}
986
987/// `.scrollPosition(_:anchor:)` — scroll to an edge, and stay bound to it.
988///
989/// [`scroll_position_id`] is the other overload, which scrolls to a row by its
990/// identity. This one takes a whole `ScrollPosition`, which is what
991/// "scrolled to the top" is: there is no row to name.
992///
993/// The position lives in the binding for the life of the view. SwiftUI writes
994/// back as the user scrolls, so it has to be somewhere that outlives the call
995/// that made it.
996pub fn scroll_position_edge(view: &ViewHandle, edge: super::Edge) -> ViewHandle {
997    unsafe {
998        let meta = abi::metadata0(resolve::scroll_position_ma());
999        assert!(!meta.is_null(), "ScrollPosition metadata");
1000        let mut initial = vec![0u8; abi::type_bytes(meta)];
1001        // `init(idType:edge:)`: x0 = the id metatype, x1 = the `Edge` tag,
1002        // x2 = T.Type and x3 = T: Hashable for the generic `some Hashable`.
1003        // `Never` is what Swift's own default argument passes.
1004        core::arch::asm!("blr {f}", f = in(reg) resolve::scroll_position_edge_init(),
1005            in("x0") resolve::never_meta(),
1006            in("x1") edge as u64,
1007            in("x2") resolve::never_meta(),
1008            in("x3") resolve::never_hashable_wt(),
1009            in("x8") initial.as_mut_ptr(),
1010            lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
1011            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
1012            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
1013            lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
1014            clobber_abi("C"),
1015        );
1016
1017        let cell: &'static std::sync::Mutex<Vec<u8>> =
1018            Box::leak(Box::new(std::sync::Mutex::new(initial)));
1019        let get_cell = cell;
1020        let binding = super::binding_get_set(
1021            meta,
1022            b"7SwiftUI7BindingVyAA14ScrollPositionVG",
1023            Box::new(move |out| {
1024                let v = get_cell.lock().expect("scroll position");
1025                core::ptr::copy_nonoverlapping(v.as_ptr(), out, v.len());
1026            }),
1027            Box::new(move |inp| {
1028                let mut v = cell.lock().expect("scroll position");
1029                let n = v.len();
1030                core::ptr::copy_nonoverlapping(inp, v.as_mut_ptr(), n);
1031            }),
1032        );
1033
1034        let subst = [view.meta, view.wt];
1035        let (out_meta, out_wt, size) =
1036            opaque_result_type(resolve::scroll_position_oret(), &subst);
1037        let mut result = vec![0u8; size];
1038        let this = view.value_ptr();
1039        // x0 = @in Binding, x1/x2 = the `UnitPoint` payload and x3 its tag
1040        // (1 = nil, which is the default anchor), x4 = Self.Type,
1041        // x5 = Self: View WT, x8 = @out, x20 = self.
1042        core::arch::asm!("blr {f}", f = in(reg) resolve::scroll_position_fn(),
1043            in("x0") binding.as_ptr(),
1044            in("x1") 0u64,
1045            in("x2") 0u64,
1046            in("x3") 1u64,
1047            in("x4") view.meta,
1048            in("x5") view.wt,
1049            in("x8") result.as_mut_ptr(),
1050            in("x20") this,
1051            lateout("x6") _, lateout("x7") _,
1052            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
1053            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
1054            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
1055        );
1056        wrap_owned(result, out_meta, out_wt)
1057    }
1058}
1059
1060/// The text a `draggable` autoclosure hands back, and the closure that makes
1061/// a fresh `+1` copy of it on each call.
1062struct DragText {
1063    text: String,
1064}
1065
1066/// The `() -> String` autoclosure body.
1067///
1068/// A new Swift string each time: the value is returned `+1`, and SwiftUI calls
1069/// this once per drag. Handing back the same two words twice would over-release
1070/// one allocation.
1071///
1072/// # Safety
1073/// `payload` must be a leaked [`DragText`].
1074unsafe extern "C" fn emit_drag_text(payload: *mut c_void) -> abi::Pair {
1075    unsafe {
1076        let d = &*(payload as *const DragText);
1077        let s = abi::swift_string(&d.text);
1078        abi::Pair(
1079            u64::from_le_bytes(s[..8].try_into().expect("lo")),
1080            u64::from_le_bytes(s[8..].try_into().expect("hi")),
1081        )
1082    }
1083}
1084
1085/// Apply `.draggable(_:)` with a `String` payload.
1086///
1087/// The overload everything else calls `draggable`: drag this view and a piece
1088/// of text comes out, droppable anywhere that takes one. The
1089/// `containerItemID:` binding next door is a different modifier for a
1090/// different job — reordering within a container.
1091///
1092/// `String` because it is the `Transferable` conformer that needs no bridging:
1093/// its witness is exported from CoreTransferable, and its value is two loadable
1094/// words. A `Data` or a `URL` payload would each need their own.
1095pub fn draggable_text(view: &ViewHandle, text: &str) -> ViewHandle {
1096    unsafe {
1097        // SwiftUI keeps the autoclosure for as long as the view lives.
1098        let ctx = Box::leak(Box::new(abi::PairReturnClosureCtx::new(
1099            emit_drag_text,
1100            Box::into_raw(Box::new(DragText { text: text.to_string() })) as *mut c_void,
1101        ))) as *mut abi::PairReturnClosureCtx;
1102        generic1_modifier_ints(
1103            resolve::draggable_fn(),
1104            resolve::draggable_oret(),
1105            view,
1106            resolve::string_meta(),
1107            resolve::string_transferable_wt(),
1108            &[abi::pair_return_closure_thunk as *const () as u64, ctx as u64],
1109            [0.0; 4],
1110        )
1111    }
1112}
1113
1114/// Apply `.draggable(containerItemID:containerNamespace:)`.
1115///
1116/// `Sendable` constrains the signature but emits no witness table, so the
1117/// `Hashable` one is the only conformance to pass.
1118pub fn draggable_container_item(view: &ViewHandle, id: HashableValue<'_>) -> ViewHandle {
1119    unsafe {
1120        let (bytes, meta, wt) = id.parts();
1121        // `Namespace.ID?` is two words: the id and a discriminator.
1122        let ns = crate::type_by_name!(swift_type!(SwiftUI.Namespace.ID).as_bytes());
1123        let opt = crate::type_by_name!(swift_type!(opt SwiftUI.Namespace.ID).as_bytes());
1124        let mut buf = vec![0u8; abi::type_bytes(opt)];
1125        abi::store_optional_tag(&mut buf, ns, false);
1126        let words: Vec<u64> = (0..abi::type_bytes(opt) / 8)
1127            .map(|i| u64::from_le_bytes(buf[i * 8..i * 8 + 8].try_into().expect("word")))
1128            .collect();
1129        let mut ints = vec![bytes.as_ptr() as u64];
1130        ints.extend(words);
1131        generic1_modifier_ints(
1132            resolve::draggable_container_fn(),
1133            resolve::draggable_container_oret(),
1134            view,
1135            meta,
1136            wt,
1137            &ints,
1138            [0.0; 4],
1139        )
1140    }
1141}
1142
1143/// `(Proxy) -> Int` — measure the proxy, ask the Rust callback, and write the
1144/// answer through x8.
1145///
1146/// The proxy arrives in x0 and used to be ignored, which made the modifier
1147/// unable to do the one thing it is for: `measure` could return a constant or
1148/// read a global, and never the geometry it was being handed. `GeometryReader`
1149/// next door had read the proxy all along, so the accessor was already there.
1150pub(crate) unsafe extern "C" fn emit_measured_int(out: *mut u8, proxy: i64, payload: *mut c_void) {
1151    unsafe {
1152        let f = boxed_ref::<dyn Fn(f64, f64) -> i64>(payload);
1153        let (w, h) = crate::views::geometry_proxy_size(proxy as *const u8);
1154        *(out as *mut i64) = f(w, h);
1155    }
1156}
1157
1158/// `(Proxy) -> Double` — the same as [`emit_measured_int`] for a `Double`.
1159///
1160/// `onGeometryChange<T>` returns `T` indirectly whatever `T` is, because it is
1161/// generic — so this differs from the integer one only in what it writes.
1162pub(crate) unsafe extern "C" fn emit_measured_f64(out: *mut u8, proxy: i64, payload: *mut c_void) {
1163    unsafe {
1164        let f = boxed_ref::<dyn Fn(f64, f64) -> f64>(payload);
1165        let (w, h) = crate::views::geometry_proxy_size(proxy as *const u8);
1166        *(out as *mut f64) = f(w, h);
1167    }
1168}
1169
1170/// `(Double, Double) -> ()` — hand the old and new values over.
1171pub(crate) unsafe extern "C" fn invoke_f64_pair(
1172    a: *const u8,
1173    b: *const u8,
1174    payload: *mut c_void,
1175) {
1176    unsafe {
1177        let f = boxed_ref::<dyn Fn(f64, f64)>(payload);
1178        f(*(a as *const f64), *(b as *const f64));
1179    }
1180}
1181
1182/// `(T, T) -> ()` — hand the old and new values to the Rust callback.
1183///
1184/// Both arrive by address because `T` is generic here. `Int` is what this
1185/// binding instantiates it with, so both are one word to load.
1186pub(crate) unsafe extern "C" fn invoke_int_pair(
1187    a: *const u8,
1188    b: *const u8,
1189    payload: *mut c_void,
1190) {
1191    unsafe {
1192        let f = boxed_ref::<dyn Fn(i64, i64)>(payload);
1193        f(*(a as *const i64), *(b as *const i64));
1194    }
1195}
1196
1197/// Apply `.onGeometryChange(for:of:action:)`.
1198///
1199/// `measure` turns the proxy's size into the value to watch; `action` is called
1200/// with the old and the new one whenever it changes.
1201///
1202/// `measure` returns the generic `T`, so it returns *through x8*, with the
1203/// proxy in x0 — which is why it needs the index thunk rather than the plain
1204/// one.
1205pub fn on_geometry_change_int(
1206    view: &ViewHandle,
1207    measure: impl Fn(f64, f64) -> i64 + 'static,
1208    action: impl Fn(i64, i64) + 'static,
1209) -> ViewHandle {
1210    unsafe {
1211        let meta = resolve::int_meta();
1212        let wt = resolve::int_equatable_wt();
1213        let of = Box::leak(Box::new(abi::IndexClosureCtx::new(
1214            emit_measured_int,
1215            boxed_ctx(Box::new(measure) as Box<dyn Fn(f64, f64) -> i64>),
1216        ))) as *mut abi::IndexClosureCtx;
1217        let act = pair_words(invoke_int_pair, boxed_ctx(Box::new(action) as Box<dyn Fn(i64, i64)>));
1218        generic1_modifier_ints(
1219            resolve::on_geometry_change_fn(),
1220            resolve::on_geometry_change_oret(),
1221            view,
1222            meta,
1223            wt,
1224            &[
1225                meta as u64,
1226                abi::index_closure_thunk as *const () as u64,
1227                of as u64,
1228                act[0],
1229                act[1],
1230            ],
1231            [0.0; 4],
1232        )
1233    }
1234}
1235
1236/// Apply `.onGeometryChange(for: Double.self, of:action:)`.
1237///
1238/// `for:` was `Int` and only `Int`, so a width — which is what the modifier is
1239/// nearly always used to watch — had to be rounded to be watched at all.
1240/// Swift's constraint on `T` is `Equatable`, and `Double` has a witness.
1241pub fn on_geometry_change_f64(
1242    view: &ViewHandle,
1243    measure: impl Fn(f64, f64) -> f64 + 'static,
1244    action: impl Fn(f64, f64) + 'static,
1245) -> ViewHandle {
1246    unsafe {
1247        let meta = resolve::double_meta();
1248        let wt = resolve::double_equatable_wt();
1249        let of = Box::leak(Box::new(abi::IndexClosureCtx::new(
1250            emit_measured_f64,
1251            boxed_ctx(Box::new(measure) as Box<dyn Fn(f64, f64) -> f64>),
1252        ))) as *mut abi::IndexClosureCtx;
1253        let act = pair_words(invoke_f64_pair, boxed_ctx(Box::new(action) as Box<dyn Fn(f64, f64)>));
1254        generic1_modifier_ints(
1255            resolve::on_geometry_change_fn(),
1256            resolve::on_geometry_change_oret(),
1257            view,
1258            meta,
1259            wt,
1260            &[
1261                meta as u64,
1262                abi::index_closure_thunk as *const () as u64,
1263                of as u64,
1264                act[0],
1265                act[1],
1266            ],
1267            [0.0; 4],
1268        )
1269    }
1270}
1271
1272/// A `Foundation.JSONDecoder`, with the `ObservableObject` conformance this
1273/// crate synthesizes.
1274///
1275/// The object used to be `ImageRenderer<AnyView>` — the one SwiftUI class that
1276/// already conforms — which reached the modifiers but could never be checked
1277/// against anything: reflecting into a live `ImageRenderer` faults, so all
1278/// three object modifiers sat outside the parity suite. A `JSONDecoder` holds
1279/// nothing but its own options and dumps in two lines, and Swift can declare
1280/// the same conformance retroactively, so the reference builds the same value.
1281///
1282/// See [`crate::conformance::observable_object`] for the table.
1283unsafe fn observable_object() -> (u64, *const c_void, *const c_void) {
1284    unsafe {
1285        let conformance = crate::conformance::observable_object();
1286        let meta = abi::metadata0(resolve::json_decoder_ma());
1287        assert!(!meta.is_null(), "JSONDecoder metadata");
1288        // A class initialiser: the metatype in x20, the new instance in x0.
1289        let object = abi::call_regs(resolve::json_decoder_init(), abi::ints(&[]), [0.0; 4], meta as u64)[0];
1290        (object, meta, conformance.witness_table())
1291    }
1292}
1293
1294/// Apply a modifier taking an `ObservableObject`.
1295unsafe fn object_modifier(
1296    func: *const c_void,
1297    oret: *const c_void,
1298    view: &ViewHandle,
1299) -> ViewHandle {
1300    unsafe {
1301        let (object, meta, wt) = observable_object();
1302        // `ObservableObject` refines `AnyObject`, so `B` is class-constrained
1303        // and loadable: the reference goes in a register, not by address.
1304        generic1_modifier_ints(func, oret, view, meta, wt, &[object], [0.0; 4])
1305    }
1306}
1307
1308/// Apply `.environmentObject(_:)`.
1309pub fn environment_object(view: &ViewHandle) -> ViewHandle {
1310    unsafe {
1311        object_modifier(resolve::environment_object_fn(), resolve::environment_object_oret(), view)
1312    }
1313}
1314
1315/// Apply `.focusedObject(_:)`.
1316pub fn focused_object(view: &ViewHandle) -> ViewHandle {
1317    unsafe {
1318        object_modifier(resolve::focused_object_fn(), resolve::focused_object_oret(), view)
1319    }
1320}
1321
1322/// Apply `.focusedSceneObject(_:)`.
1323pub fn focused_scene_object(view: &ViewHandle) -> ViewHandle {
1324    unsafe {
1325        object_modifier(
1326            resolve::focused_scene_object_fn(),
1327            resolve::focused_scene_object_oret(),
1328            view,
1329        )
1330    }
1331}
1332
1333/// Apply `.accessibilityDefaultFocus(_:_:)`.
1334///
1335/// The binding's `Value` is generic here, which makes `FocusState<A>.Binding`
1336/// address-only — unlike the concrete `focused(_:)`, where it travels in three
1337/// registers.
1338pub fn accessibility_default_focus(view: &ViewHandle, value: bool) -> ViewHandle {
1339    unsafe {
1340        let mut binding = accessibility_focus_binding();
1341        let mut v = u64::from(value);
1342        generic1_modifier_ints(
1343            resolve::a11y_default_focus_fn(),
1344            resolve::a11y_default_focus_oret(),
1345            view,
1346            resolve::bool_meta(),
1347            resolve::bool_hashable_wt(),
1348            &[binding.as_mut_ptr() as u64, &mut v as *mut u64 as u64],
1349            [0.0; 4],
1350        )
1351    }
1352}
1353
1354/// The three words of a fresh `AccessibilityFocusState<Bool>.Binding`.
1355pub(crate) unsafe fn accessibility_focus_binding() -> [u64; 3] {
1356    unsafe {
1357        focus_binding(
1358            mangle::bound(&swift_type!(SwiftUI.AccessibilityFocusState), &[mangle::BOOL]).as_bytes(),
1359            resolve::a11y_focus_state_bool_init(),
1360            resolve::a11y_focus_state_projected(),
1361        )
1362    }
1363}
1364
1365
1366/// Apply `.layoutValue(key:value:)` with a runtime-synthesized key.
1367///
1368/// A second protocol through the same machinery as [`preference_int`], to show
1369/// nothing about it is specific to `PreferenceKey`.
1370pub fn layout_value_int(view: &ViewHandle, value: i64) -> ViewHandle {
1371    unsafe {
1372        let wt = crate::conformance::layout_value_key().witness_table();
1373        let meta = resolve::divider_meta();
1374        let mut boxed = value;
1375        generic1_modifier_ints(
1376            resolve::layout_value_fn(),
1377            resolve::layout_value_oret(),
1378            view,
1379            meta,
1380            wt,
1381            &[meta as u64, &mut boxed as *mut i64 as u64],
1382            [0.0; 4],
1383        )
1384    }
1385}
1386
1387/// Apply a modifier taking a value of a protocol this crate conforms to at
1388/// run time, using `EmptyView` as the conforming type.
1389unsafe fn synthesized_value_modifier(
1390    func: *const c_void,
1391    oret: *const c_void,
1392    view: &ViewHandle,
1393    wt: *const c_void,
1394) -> ViewHandle {
1395    unsafe {
1396        // `EmptyView` has no stored properties, so the value is zero bytes —
1397        // but the callee still takes it by address, so it needs somewhere to
1398        // point.
1399        let mut empty = 0u64;
1400        generic1_modifier_ints(
1401            func,
1402            oret,
1403            view,
1404            resolve::empty_meta(),
1405            wt,
1406            &[&mut empty as *mut u64 as u64],
1407            [0.0; 4],
1408        )
1409    }
1410}
1411
1412/// Apply `.presentationSizing(_:)`.
1413pub fn presentation_sizing(view: &ViewHandle) -> ViewHandle {
1414    unsafe {
1415        synthesized_value_modifier(
1416            resolve::presentation_sizing_fn(),
1417            resolve::presentation_sizing_oret(),
1418            view,
1419            crate::conformance::presentation_sizing().witness_table(),
1420        )
1421    }
1422}
1423
1424/// Apply `.previewContext(_:)`.
1425pub fn preview_context(view: &ViewHandle) -> ViewHandle {
1426    unsafe {
1427        synthesized_value_modifier(
1428            resolve::preview_context_fn(),
1429            resolve::preview_context_oret(),
1430            view,
1431            crate::conformance::preview_context().witness_table(),
1432        )
1433    }
1434}
1435
1436/// The `WritableKeyPath<EnvironmentValues, Image.Scale>` SwiftUI itself builds.
1437///
1438/// Constructing a key path from nothing means emitting a key path *pattern* and
1439/// handing it to `swift_getKeyPath` — a compact format describing each
1440/// component, which for a resilient computed property has to reference the
1441/// property descriptor. There is an easier way in, because SwiftUI already
1442/// builds the ones this crate needs: `.imageScale(_:)` returns
1443/// `_EnvironmentKeyWritingModifier<Image.Scale>`, whose first stored property
1444/// *is* that key path. So the modifier is applied once and the key path read
1445/// back out of the result.
1446///
1447/// The layout is `ModifiedContent<AnyView, _EnvironmentKeyWritingModifier<V>>`:
1448/// eight bytes of `AnyView`, then the key path, then the value.
1449unsafe fn image_scale_key_path() -> u64 {
1450    use std::sync::atomic::{AtomicU64, Ordering};
1451    static CACHE: AtomicU64 = AtomicU64::new(0);
1452    unsafe {
1453        let cached = CACHE.load(Ordering::Relaxed);
1454        if cached != 0 {
1455            return cached;
1456        }
1457        let meta = abi::metadata0(resolve::image_scale_ma());
1458        let mut arg = vec![0u8; abi::type_bytes(meta)];
1459        abi::inject_enum_tag(&mut arg, meta, 2);
1460        // `EmptyView`, not `Text`: the key path is read back out of the
1461        // modifier, which `ModifiedContent` lays out after the content. An
1462        // empty host puts it at offset 0 regardless of what the receiver's
1463        // type would otherwise contribute.
1464        let host = super::empty_view();
1465        let subst = [host.meta, host.wt];
1466        let (_, _, size) = opaque_result_type(resolve::image_scale_oret(), &subst);
1467        let r = abi::call_modifier(
1468            resolve::image_scale_fn(),
1469            host.value_ptr(),
1470            host.meta,
1471            host.wt,
1472            &[arg.as_ptr() as u64],
1473            [0.0; 4],
1474            size,
1475        );
1476        let kp = u64::from_le_bytes(r[0..8].try_into().expect("key path word"));
1477        // The result is dropped with this scope; the key path has to outlive it.
1478        swift_runtime_sys::RuntimeRaw::swift_retain(kp as *mut c_void);
1479        CACHE.store(kp, Ordering::Relaxed);
1480        kp
1481    }
1482}
1483
1484/// The `\EnvironmentValues.symbolVariants` key path.
1485///
1486/// `EnvironmentValues` exports no properties, so there is no key path to build
1487/// — but SwiftUI stores one in every modifier that writes the environment, and
1488/// `.symbolVariant(_:)` is such a modifier. Calling it on a throwaway view and
1489/// reading the key path back out of the result is the same trick
1490/// [`image_scale_key_path`] uses; the only difference is that this one is a
1491/// *transform* modifier rather than a writing one, which changes what follows
1492/// the key path and not the key path's position.
1493unsafe fn symbol_variants_key_path() -> u64 {
1494    use std::sync::atomic::{AtomicU64, Ordering};
1495    static CACHE: AtomicU64 = AtomicU64::new(0);
1496    unsafe {
1497        let cached = CACHE.load(Ordering::Relaxed);
1498        if cached != 0 {
1499            return cached;
1500        }
1501        let meta = abi::metadata0(resolve::symbol_variants_ma());
1502        let mut arg = super::static_getter_indirect(
1503            resolve::require(super::SymbolVariants::Fill.getter()),
1504            meta,
1505            0,
1506        );
1507        // `EmptyView`, not `Text`: the key path is read back out of the
1508        // modifier, which `ModifiedContent` lays out after the content. An
1509        // empty host puts it at offset 0 regardless of what the receiver's
1510        // type would otherwise contribute.
1511        let host = super::empty_view();
1512        let subst = [host.meta, host.wt];
1513        let (_, _, size) = opaque_result_type(resolve::symbol_variant_oret(), &subst);
1514        let r = abi::call_modifier(
1515            resolve::symbol_variant_fn(),
1516            host.value_ptr(),
1517            host.meta,
1518            host.wt,
1519            &[arg.as_mut_ptr() as u64],
1520            [0.0; 4],
1521            size,
1522        );
1523        // `ModifiedContent`: an empty content, then the modifier, whose first
1524        // field is the key path.
1525        let kp = u64::from_le_bytes(r[0..8].try_into().expect("key path word"));
1526        swift_runtime_sys::RuntimeRaw::swift_retain(kp as *mut c_void);
1527        CACHE.store(kp, Ordering::Relaxed);
1528        kp
1529    }
1530}
1531
1532/// Apply `.environment(\.symbolVariants, _)`.
1533pub fn environment_symbol_variants(view: &ViewHandle, value: super::SymbolVariants) -> ViewHandle {
1534    unsafe {
1535        let meta = abi::metadata0(resolve::symbol_variants_ma());
1536        let arg = super::static_getter_indirect(value.resolved(), meta, 0);
1537        generic1_modifier_ints(
1538            resolve::environment_fn(),
1539            resolve::environment_oret(),
1540            view,
1541            meta,
1542            core::ptr::null(),
1543            &[symbol_variants_key_path(), arg.as_ptr() as u64],
1544            [0.0; 4],
1545        )
1546    }
1547}
1548
1549/// Apply `.environment(_:_:)` for `\EnvironmentValues.imageScale`.
1550///
1551/// The value is a generic `V` from the callee's side, so it travels by address
1552/// however small `Image.Scale` turns out to be.
1553pub fn environment_image_scale(view: &ViewHandle, scale: ImageScale) -> ViewHandle {
1554    unsafe {
1555        let meta = abi::metadata0(resolve::image_scale_ma());
1556        let mut value = vec![0u8; abi::type_bytes(meta)];
1557        abi::inject_enum_tag(&mut value, meta, scale.tag());
1558        generic1_modifier_ints(
1559            resolve::environment_fn(),
1560            resolve::environment_oret(),
1561            view,
1562            meta,
1563            core::ptr::null(),
1564            &[image_scale_key_path(), value.as_ptr() as u64],
1565            [0.0; 4],
1566        )
1567    }
1568}
1569
1570/// `(inout ImageScale) -> ()` — read the current scale, ask Rust, write it back.
1571///
1572/// The whole point of `transformEnvironment` is that the value is `inout`: the
1573/// closure is handed the environment's current setting and replaces it. The
1574/// binding dropped the argument, so it could observe that the transform ran and
1575/// never change anything — an `.environment` that could not set.
1576pub(crate) unsafe extern "C" fn transform_image_scale(
1577    a0: u64,
1578    _a1: u64,
1579    _a2: u64,
1580    payload: *mut c_void,
1581) {
1582    unsafe {
1583        let f = boxed_ref::<dyn Fn(ImageScale) -> ImageScale>(payload);
1584        let meta = abi::metadata0(resolve::image_scale_ma());
1585        let p = a0 as *mut u8;
1586        let current = match abi::read_enum_tag(p, meta) {
1587            0 => ImageScale::Small,
1588            1 => ImageScale::Medium,
1589            _ => ImageScale::Large,
1590        };
1591        let next = f(current);
1592        let size = abi::type_bytes(meta).max(1);
1593        abi::inject_enum_tag(core::slice::from_raw_parts_mut(p, size), meta, next as u32);
1594    }
1595}
1596
1597/// Apply `.transformEnvironment(_:_:)` for `\EnvironmentValues.imageScale`.
1598pub fn transform_environment_image_scale(
1599    view: &ViewHandle,
1600    action: impl Fn(ImageScale) -> ImageScale + 'static,
1601) -> ViewHandle {
1602    unsafe {
1603        let meta = abi::metadata0(resolve::image_scale_ma());
1604        let c = wide_words(
1605            transform_image_scale,
1606            boxed_ctx(Box::new(action) as Box<dyn Fn(ImageScale) -> ImageScale>),
1607        );
1608        generic1_modifier_ints(
1609            resolve::transform_environment_fn(),
1610            resolve::transform_environment_oret(),
1611            view,
1612            meta,
1613            core::ptr::null(),
1614            &[image_scale_key_path(), c[0], c[1]],
1615            [0.0; 4],
1616        )
1617    }
1618}
1619
1620/// What a `transform_environment` handler needs, erased of its key type.
1621struct TransformCtx {
1622    /// Decode the byte the enum is stored as, run the handler, encode again.
1623    step: Box<dyn Fn(u8) -> u8>,
1624}
1625
1626/// The `(inout V) -> ()` body SwiftUI calls.
1627///
1628/// Every environment property this crate has a key for stores one byte — an
1629/// enum tag or a `Bool` — so the whole transform is byte in, byte out, with
1630/// the key's own `decode`/`encode` on either side. A property whose value is
1631/// wider would need its own path; `buffer_bytes` is what would say so.
1632///
1633/// # Safety
1634/// `payload` must be a leaked [`TransformCtx`], and `a0` a pointer to a live
1635/// value of the property's type.
1636unsafe extern "C" fn transform_env_byte(a0: u64, _a1: u64, _a2: u64, payload: *mut c_void) {
1637    unsafe {
1638        let ctx = &*(payload as *const TransformCtx);
1639        let p = a0 as *mut u8;
1640        *p = (ctx.step)(*p);
1641    }
1642}
1643
1644/// `.transformEnvironment(_:transform:)` for any property this crate has a
1645/// key for.
1646///
1647/// [`transform_environment_image_scale`] does this for one property, with the
1648/// key path recovered from a modifier SwiftUI built. Every `EnvKey` already
1649/// carries its own key path for `@Environment` to read through, so the same
1650/// modifier works for all of them — the only thing that was missing was a way
1651/// to write the value back, which is what [`crate::state::EnvKey::encode`] is.
1652///
1653/// ```no_run
1654/// # use swiftui_native::{state::ControlSizeKey, views::{self, ControlSize}};
1655/// // One size larger than whatever is in scope.
1656/// views::transform_environment::<ControlSizeKey>(&views::text("x"), |s| match s {
1657///     ControlSize::Mini => ControlSize::Small,
1658///     other => other,
1659/// });
1660/// ```
1661pub fn transform_environment<K: crate::state::EnvKey>(
1662    view: &ViewHandle,
1663    action: impl Fn(K::Value) -> K::Value + 'static,
1664) -> ViewHandle {
1665    unsafe {
1666        assert_eq!(
1667            K::buffer_bytes(),
1668            1,
1669            "transform_environment handles one-byte values; this key's is wider"
1670        );
1671        let meta = abi::resolve_type_by_mangled_name(K::value_type());
1672        assert!(!meta.is_null(), "environment value metadata");
1673        let ctx = Box::leak(Box::new(TransformCtx {
1674            step: Box::new(move |b| K::encode(&action(K::decode(&[b])))),
1675        })) as *mut TransformCtx;
1676        let c = wide_words(transform_env_byte, ctx as *mut c_void);
1677        generic1_modifier_ints(
1678            resolve::transform_environment_fn(),
1679            resolve::transform_environment_oret(),
1680            view,
1681            meta,
1682            core::ptr::null(),
1683            &[K::key_path(), c[0], c[1]],
1684            [0.0; 4],
1685        )
1686    }
1687}
1688
1689/// Apply `.dragContainerSelection(_:containerNamespace:)` with the selection.
1690pub fn drag_container_selection(view: &ViewHandle, selected: &[&str]) -> ViewHandle {
1691    unsafe {
1692        let meta = resolve::string_meta();
1693        let wt = resolve::string_hashable_wt();
1694        let selection = word_closure(emit_strings, strings_payload(swift_string_array(selected)));
1695        let ns = crate::type_by_name!(swift_type!(SwiftUI.Namespace.ID).as_bytes());
1696        let opt = crate::type_by_name!(swift_type!(opt SwiftUI.Namespace.ID).as_bytes());
1697        let mut buf = vec![0u8; abi::type_bytes(opt)];
1698        abi::store_optional_tag(&mut buf, ns, false);
1699        let mut ints = vec![selection[0], selection[1]];
1700        ints.extend((0..abi::type_bytes(opt) / 8).map(|i| {
1701            u64::from_le_bytes(buf[i * 8..i * 8 + 8].try_into().expect("word"))
1702        }));
1703        generic1_modifier_ints(
1704            resolve::drag_container_selection_fn(),
1705            resolve::drag_container_selection_oret(),
1706            view,
1707            meta,
1708            wt,
1709            &ints,
1710            [0.0; 4],
1711        )
1712    }
1713}
1714
1715/// What a scroll view's geometry looks like when it changes.
1716///
1717/// `ScrollGeometry` exports no accessors, but it does not need to: the fields
1718/// are stored, and the metadata says where.
1719///
1720/// ```text
1721///   +0   contentOffset  CGPoint
1722///   +16  contentSize    CGSize
1723///   +32  contentInsets  EdgeInsets  (top, leading, bottom, trailing)
1724///   +64  containerSize  CGSize
1725///   +80  visibleRect    CGRect      (origin then size)
1726/// ```
1727#[derive(Clone, Copy, Debug, Default, PartialEq)]
1728pub struct ScrollGeometry {
1729    /// How far the content has been scrolled.
1730    pub content_offset: (f64, f64),
1731    /// How large the scrollable content is.
1732    pub content_size: (f64, f64),
1733    /// The insets around it, as `(top, leading, bottom, trailing)`.
1734    pub content_insets: (f64, f64, f64, f64),
1735    /// How large the scroll view itself is.
1736    pub container_size: (f64, f64),
1737    /// What part of the content is on screen, as `(x, y, width, height)`.
1738    pub visible_rect: (f64, f64, f64, f64),
1739}
1740
1741/// The offsets of `ScrollGeometry`'s stored fields, from its metadata.
1742fn scroll_geometry_offsets() -> Option<&'static [usize; 5]> {
1743    static CACHE: std::sync::OnceLock<Option<[usize; 5]>> = std::sync::OnceLock::new();
1744    CACHE
1745        .get_or_init(|| unsafe {
1746            let offs = abi::struct_field_offsets(abi::metadata0(resolve::scroll_geometry_ma()))?;
1747            (offs.len() == 5).then(|| [offs[0], offs[1], offs[2], offs[3], offs[4]])
1748        })
1749        .as_ref()
1750}
1751
1752/// Read the geometry at `p`, which SwiftUI passed by address.
1753///
1754/// # Safety
1755/// `p` must point at a live `ScrollGeometry`.
1756pub(crate) unsafe fn read_scroll_geometry(p: *const u8) -> ScrollGeometry {
1757    let Some(&[offset, size, insets, container, visible]) = scroll_geometry_offsets() else {
1758        return ScrollGeometry::default();
1759    };
1760    unsafe {
1761        let at = |o: usize| *(p.add(o) as *const f64);
1762        let pair = |o: usize| (at(o), at(o + 8));
1763        ScrollGeometry {
1764            content_offset: pair(offset),
1765            content_size: pair(size),
1766            content_insets: (at(insets), at(insets + 8), at(insets + 16), at(insets + 24)),
1767            container_size: pair(container),
1768            visible_rect: (at(visible), at(visible + 8), at(visible + 16), at(visible + 24)),
1769        }
1770    }
1771}
1772
1773/// `(ScrollGeometry) -> Int` — measure the geometry and write through x8.
1774///
1775/// The geometry arrives in x0 by address, because it is a 112-byte resilient
1776/// struct. It used to be ignored, which left the modifier able to notice that
1777/// *something* had scrolled and nothing more.
1778pub(crate) unsafe extern "C" fn emit_scroll_measure(out: *mut u8, geometry: i64, payload: *mut c_void) {
1779    unsafe {
1780        let f = boxed_ref::<dyn Fn(ScrollGeometry) -> i64>(payload);
1781        *(out as *mut i64) = f(read_scroll_geometry(geometry as *const u8));
1782    }
1783}
1784
1785/// Apply `.onScrollGeometryChange(for:prefersLast:of:action:)`.
1786pub fn on_scroll_geometry_change(
1787    view: &ViewHandle,
1788    measure: impl Fn(ScrollGeometry) -> i64 + 'static,
1789    action: impl Fn(i64, i64) + 'static,
1790) -> ViewHandle {
1791    unsafe {
1792        let meta = resolve::int_meta();
1793        let wt = resolve::int_equatable_wt();
1794        let of = Box::leak(Box::new(abi::IndexClosureCtx::new(
1795            emit_scroll_measure,
1796            boxed_ctx(Box::new(measure) as Box<dyn Fn(ScrollGeometry) -> i64>),
1797        ))) as *mut abi::IndexClosureCtx;
1798        let act = pair_words(invoke_int_pair, boxed_ctx(Box::new(action) as Box<dyn Fn(i64, i64)>));
1799        generic1_modifier_ints(
1800            resolve::on_scroll_geometry_change_fn(),
1801            resolve::on_scroll_geometry_change_oret(),
1802            view,
1803            meta,
1804            wt,
1805            &[
1806                meta as u64,
1807                abi::index_closure_thunk as *const () as u64,
1808                of as u64,
1809                act[0],
1810                act[1],
1811            ],
1812            [0.0; 4],
1813        )
1814    }
1815}
1816
1817/// Apply `.onScrollGeometryChange(for: Double.self, prefersLast:of:action:)`.
1818pub fn on_scroll_geometry_change_f64(
1819    view: &ViewHandle,
1820    measure: impl Fn(ScrollGeometry) -> f64 + 'static,
1821    action: impl Fn(f64, f64) + 'static,
1822) -> ViewHandle {
1823    unsafe {
1824        let meta = resolve::double_meta();
1825        let wt = resolve::double_equatable_wt();
1826        let of = Box::leak(Box::new(abi::IndexClosureCtx::new(
1827            emit_scroll_measure_f64,
1828            boxed_ctx(Box::new(measure) as Box<dyn Fn(ScrollGeometry) -> f64>),
1829        ))) as *mut abi::IndexClosureCtx;
1830        let act = pair_words(invoke_f64_pair, boxed_ctx(Box::new(action) as Box<dyn Fn(f64, f64)>));
1831        generic1_modifier_ints(
1832            resolve::on_scroll_geometry_change_fn(),
1833            resolve::on_scroll_geometry_change_oret(),
1834            view,
1835            meta,
1836            wt,
1837            &[
1838                meta as u64,
1839                abi::index_closure_thunk as *const () as u64,
1840                of as u64,
1841                act[0],
1842                act[1],
1843            ],
1844            [0.0; 4],
1845        )
1846    }
1847}
1848
1849/// `(ScrollGeometry) -> Double` — the same as [`emit_scroll_measure`] for a
1850/// `Double`.
1851pub(crate) unsafe extern "C" fn emit_scroll_measure_f64(
1852    out: *mut u8,
1853    geometry: i64,
1854    payload: *mut c_void,
1855) {
1856    unsafe {
1857        let f = boxed_ref::<dyn Fn(ScrollGeometry) -> f64>(payload);
1858        *(out as *mut f64) = f(read_scroll_geometry(geometry as *const u8));
1859    }
1860}
1861
1862/// How big the view being aligned is.
1863///
1864/// SwiftUI hands an alignment guide a `ViewDimensions`, and the guide is
1865/// expected to answer *in terms of it* — `d.width / 2` centres, `d.width`
1866/// puts the edge at the far side. The binding ignored it, so a guide could
1867/// only return a constant, which is the one kind of guide that does not need
1868/// the modifier at all.
1869///
1870/// `width` and `height` are read through SwiftUI's own getters. The two
1871/// remaining accessors — the `[HorizontalAlignment]` and `[VerticalAlignment]`
1872/// subscripts, which is how Swift writes `d[.leading]` — are `@inlinable` and
1873/// export no symbol, so they are not here.
1874#[derive(Clone, Copy, Debug, Default, PartialEq)]
1875pub struct ViewDimensions {
1876    /// The view's width.
1877    pub width: f64,
1878    /// The view's height.
1879    pub height: f64,
1880}
1881
1882/// Read the dimensions at `p`, which SwiftUI passed by address.
1883///
1884/// # Safety
1885/// `p` must point at a live `ViewDimensions`.
1886pub(crate) unsafe fn read_view_dimensions(p: *const u8) -> ViewDimensions {
1887    unsafe {
1888        let get = |f: *const c_void| {
1889            let out: f64;
1890            // A getter on a 48-byte struct: self is `@in_guaranteed` in x20 and
1891            // the `CGFloat` comes back in d0.
1892            core::arch::asm!("blr {f}", f = in(reg) f,
1893                in("x20") p,
1894                lateout("d0") out,
1895                lateout("x0") _, lateout("x1") _, lateout("x2") _, lateout("x3") _,
1896                lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
1897                lateout("x8") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
1898                lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
1899                lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x21") _,
1900                clobber_abi("C"),
1901            );
1902            out
1903        };
1904        let d = ViewDimensions {
1905            width: get(resolve::view_dimensions_width()),
1906            height: get(resolve::view_dimensions_height()),
1907        };
1908        // The getters should agree with the stored layout — `size` is a
1909        // `ViewSize` whose first field is the `CGSize`. Checked in debug
1910        // builds, because if they ever disagree it means one of the two reads
1911        // is looking at the wrong thing.
1912        debug_assert_eq!(Some((d.width, d.height)), stored_view_size(p));
1913        d
1914    }
1915}
1916
1917/// `ViewDimensions.size.value`, read straight out of the struct.
1918///
1919/// Only for the crosscheck in [`read_view_dimensions`] — the getters are what
1920/// the value is actually read with.
1921///
1922/// # Safety
1923/// `p` must point at a live `ViewDimensions`.
1924unsafe fn stored_view_size(p: *const u8) -> Option<(f64, f64)> {
1925    unsafe {
1926        let offs = abi::struct_field_offsets(abi::metadata0(resolve::view_dimensions_ma()))?;
1927        // guideComputer, size.
1928        let &[_, size] = offs.as_slice() else { return None };
1929        let at = p.add(size) as *const f64;
1930        Some((*at, *at.add(1)))
1931    }
1932}
1933
1934/// `(ViewDimensions) -> CGFloat` — measure, then ask the Rust callback.
1935pub(crate) unsafe extern "C" fn compute_length(arg: u64, payload: *mut c_void) -> f64 {
1936    unsafe {
1937        let f = boxed_ref::<dyn Fn(ViewDimensions) -> f64>(payload);
1938        f(read_view_dimensions(arg as *const u8))
1939    }
1940}
1941
1942/// Apply `.alignmentGuide(_:computeValue:)`.
1943///
1944/// `computeValue` returns a `CGFloat`, which comes back in d0 — so it needs a
1945/// thunk of its own; the integer-returning one cannot carry it.
1946pub fn alignment_guide(
1947    view: &ViewHandle,
1948    alignment: HorizontalAlignment,
1949    compute: impl Fn(ViewDimensions) -> f64 + 'static,
1950) -> ViewHandle {
1951    unsafe {
1952        let meta = abi::metadata0(resolve::horizontal_alignment_ma());
1953        let a = frozen_static(meta, alignment.getter());
1954        let ctx = Box::leak(Box::new(abi::FloatClosureCtx::new(
1955            compute_length,
1956            boxed_ctx(Box::new(compute) as Box<dyn Fn(ViewDimensions) -> f64>),
1957        ))) as *mut abi::FloatClosureCtx;
1958        opaque_modifier(
1959            resolve::alignment_guide_fn(),
1960            resolve::alignment_guide_oret(),
1961            view,
1962            &[a, abi::float_closure_thunk as *const () as u64, ctx as u64],
1963            [0.0; 4],
1964        )
1965    }
1966}
1967
1968/// Apply a modifier generic over *two* parameters.
1969///
1970/// The substitution buffer lists every metadata argument before every witness
1971/// table, so a second parameter is not simply appended — it goes between the
1972/// first parameter's metadata and the first conformance.
1973unsafe fn generic_pair_modifier(
1974    func: *const c_void,
1975    oret: *const c_void,
1976    view: &ViewHandle,
1977    first: (*const c_void, *const c_void),
1978    second: (*const c_void, *const c_void),
1979    leading: &[u64],
1980) -> ViewHandle {
1981    unsafe {
1982        let subst = [
1983            view.meta,
1984            first.0,
1985            second.0,
1986            view.wt,
1987            first.1,
1988            second.1,
1989        ];
1990        let (meta, wt, size) = opaque_result_type(oret, &subst);
1991        let mut ints = leading.to_vec();
1992        ints.extend(subst.iter().map(|p| *p as u64));
1993        let r = abi::call_modifier_generic(func, view.value_ptr(), &ints, [0.0; 4], size);
1994        wrap_owned(r, meta, wt)
1995    }
1996}
1997
1998/// Apply `.backgroundPreferenceValue(_:alignment:_:)` with fixed content.
1999pub fn background_preference_value(
2000    view: &ViewHandle,
2001    alignment: Alignment,
2002    content: &ViewHandle,
2003) -> ViewHandle {
2004    unsafe { preference_value_modifier(
2005        resolve::background_preference_value_fn(),
2006        resolve::background_preference_value_oret(),
2007        view, alignment, content,
2008    ) }
2009}
2010
2011/// Apply `.overlayPreferenceValue(_:alignment:_:)` with fixed content.
2012pub fn overlay_preference_value(
2013    view: &ViewHandle,
2014    alignment: Alignment,
2015    content: &ViewHandle,
2016) -> ViewHandle {
2017    unsafe { preference_value_modifier(
2018        resolve::overlay_preference_value_fn(),
2019        resolve::overlay_preference_value_oret(),
2020        view, alignment, content,
2021    ) }
2022}
2023
2024unsafe fn preference_value_modifier(
2025    func: *const c_void,
2026    oret: *const c_void,
2027    view: &ViewHandle,
2028    alignment: Alignment,
2029    content: &ViewHandle,
2030) -> ViewHandle {
2031    unsafe {
2032        let key_meta = resolve::spacer_meta();
2033        let key_wt = crate::conformance::preference_key().witness_table();
2034        let a = alignment.words();
2035        let c = view_words(content);
2036        generic_pair_modifier(
2037            func,
2038            oret,
2039            view,
2040            (key_meta, key_wt),
2041            (content.meta, content.wt),
2042            &[key_meta as u64, a[0], a[1], c[0], c[1]],
2043        )
2044    }
2045}
2046
2047/// `(Array<A>) -> ()`.
2048///
2049/// `Array` is one word whatever its element, so it stays loadable inside a
2050/// generic function — x0 is the array itself and not a pointer to it, unlike
2051/// the bare generic parameter [`ask_about_int`] receives.
2052unsafe extern "C" fn saw_int_array(value: *const u8, payload: *mut c_void) {
2053    unsafe {
2054        let f = boxed_ref::<dyn Fn(&[i64])>(payload);
2055        let (base, count) = abi::array_parts(value as u64);
2056        f(core::slice::from_raw_parts(base as *const i64, count));
2057    }
2058}
2059
2060/// Apply `.onScrollTargetVisibilityChange(for:threshold:_:)` with `A == Int`.
2061///
2062/// `visible` receives the identities of the scroll targets that are at least
2063/// `threshold` visible, every time that set changes.
2064pub fn on_scroll_target_visibility_change(
2065    view: &ViewHandle,
2066    threshold: f64,
2067    visible: impl Fn(&[i64]) + 'static,
2068) -> ViewHandle {
2069    unsafe {
2070        let meta = resolve::int_meta();
2071        let wt = resolve::int_hashable_wt();
2072        let ctx = Box::leak(Box::new(abi::ValueClosureCtx::new(
2073            saw_int_array,
2074            boxed_ctx(Box::new(visible) as Box<dyn Fn(&[i64])>),
2075        ))) as *mut abi::ValueClosureCtx;
2076        generic1_modifier_ints(
2077            resolve::scroll_target_visibility_fn(),
2078            resolve::scroll_target_visibility_oret(),
2079            view,
2080            meta,
2081            wt,
2082            &[meta as u64, abi::value_closure_thunk as *const () as u64, ctx as u64],
2083            [threshold, 0.0, 0.0, 0.0],
2084        )
2085    }
2086}
2087
2088/// `(EmptyVisualEffect, GeometryProxy) -> A` — return the effect unchanged.
2089///
2090/// `EmptyVisualEffect` is empty, so there is nothing to copy and nothing to
2091/// write: the identity function on a zero-sized type initialises its result by
2092/// doing nothing at all.
2093unsafe extern "C" fn identity_effect(_out: *mut u8, _payload: *mut c_void) {}
2094
2095/// Apply `.visualEffect(_:)`.
2096///
2097/// The closure must return `some VisualEffect`, and the smallest real one is
2098/// `EmptyVisualEffect` — which SwiftUI exports a `VisualEffect` witness table
2099/// for but no metadata accessor, so the metadata is demangled from its name.
2100pub fn visual_effect(view: &ViewHandle) -> ViewHandle {
2101    unsafe {
2102        let meta = crate::type_by_name!(swift_type!(SwiftUI.EmptyVisualEffect).as_bytes());
2103        let wt = resolve::empty_visual_effect_wt();
2104        let ctx = Box::leak(Box::new(abi::ClosureCtx::new(
2105            identity_effect,
2106            core::ptr::null_mut(),
2107        ))) as *mut abi::ClosureCtx;
2108        generic1_modifier_ints(
2109            resolve::visual_effect_fn(),
2110            resolve::visual_effect_oret(),
2111            view,
2112            meta,
2113            wt,
2114            &[abi::swift_closure_thunk as *const () as u64, ctx as u64],
2115            [0.0; 4],
2116        )
2117    }
2118}
2119
2120/// A scroll or layout axis.
2121///
2122/// Two cases, neither with a payload, so the tags are the declaration order.
2123#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2124pub enum Axis {
2125    Horizontal = 0,
2126    Vertical = 1,
2127}
2128
2129/// How a scroll transition interpolates.
2130#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2131pub enum ScrollTransitionConfiguration {
2132    /// Follows the scroll position directly.
2133    Interactive,
2134    /// Animates between the discrete phases.
2135    Animated,
2136    /// Applies nothing.
2137    Identity,
2138}
2139
2140impl ScrollTransitionConfiguration {
2141    fn getter(self) -> *const c_void {
2142        match self {
2143            ScrollTransitionConfiguration::Interactive => resolve::scroll_transition_config_interactive(),
2144            ScrollTransitionConfiguration::Animated => resolve::scroll_transition_config_animated(),
2145            ScrollTransitionConfiguration::Identity => resolve::scroll_transition_config_identity(),
2146        }
2147    }
2148}
2149
2150/// The parts of a `scrollTransition` call that do not depend on which overload
2151/// is being made: the axis, and the identity transition.
2152///
2153/// `axis` is `Optional<Axis>`, which is one byte because `Axis` has two cases
2154/// and room to spare, so it travels in a register rather than by address.
2155///
2156/// The transition is the identity on `EmptyVisualEffect`, as in [`visual_effect`]
2157/// — a closure that returns its argument is still a real closure, and it is the
2158/// only `VisualEffect` that can be built without a Swift type of one's own.
2159unsafe fn scroll_transition_tail(axis: Option<Axis>) -> (Vec<u64>, *const c_void, *const c_void) {
2160    unsafe {
2161        let axis_meta = abi::metadata0(resolve::axis_ma());
2162        let opt_axis = crate::type_by_name!(swift_type!(opt enum SwiftUI.Axis).as_bytes());
2163        let tag = axis.map(|a| a as u32);
2164        let mut payload = vec![0u8; abi::type_bytes(axis_meta).max(1)];
2165        if let Some(t) = tag {
2166            abi::inject_enum_tag(&mut payload, axis_meta, t);
2167        }
2168        let words = optional_words(axis_meta, opt_axis, &payload, tag.is_some());
2169        let meta = crate::type_by_name!(swift_type!(SwiftUI.EmptyVisualEffect).as_bytes());
2170        let wt = resolve::empty_visual_effect_wt();
2171        let ctx = Box::leak(Box::new(abi::ClosureCtx::new(
2172            identity_effect,
2173            core::ptr::null_mut(),
2174        ))) as *mut abi::ClosureCtx;
2175        let mut ints = words;
2176        ints.push(abi::swift_closure_thunk as *const () as u64);
2177        ints.push(ctx as u64);
2178        (ints, meta, wt)
2179    }
2180}
2181
2182/// Apply `.scrollTransition(_:axis:transition:)`.
2183pub fn scroll_transition(
2184    view: &ViewHandle,
2185    config: ScrollTransitionConfiguration,
2186    axis: Option<Axis>,
2187) -> ViewHandle {
2188    unsafe {
2189        let cfg_meta = abi::metadata0(resolve::scroll_transition_config_ma());
2190        let cfg = abi::static_out(config.getter(), cfg_meta, abi::type_bytes(cfg_meta));
2191        let (tail, meta, wt) = scroll_transition_tail(axis);
2192        let mut ints = vec![cfg.as_ptr() as u64];
2193        ints.extend_from_slice(&tail);
2194        generic1_modifier_ints(
2195            resolve::scroll_transition_fn(),
2196            resolve::scroll_transition_oret(),
2197            view,
2198            meta,
2199            wt,
2200            &ints,
2201            [0.0; 4],
2202        )
2203    }
2204}
2205
2206/// Apply `.scrollTransition(topLeading:bottomTrailing:axis:transition:)` — the
2207/// two ends of the scroll configured separately.
2208///
2209/// A view leaving the top of a scroll view and one leaving the bottom are the
2210/// same transition run in opposite directions, and this is what lets them
2211/// differ: `.animated` on the way in and `.identity` on the way out, say.
2212///
2213/// Both configurations are `@in`, so both travel by address — which is why the
2214/// two buffers have to outlive the call rather than being built inline.
2215pub fn scroll_transition_ends(
2216    view: &ViewHandle,
2217    top_leading: ScrollTransitionConfiguration,
2218    bottom_trailing: ScrollTransitionConfiguration,
2219    axis: Option<Axis>,
2220) -> ViewHandle {
2221    unsafe {
2222        let cfg_meta = abi::metadata0(resolve::scroll_transition_config_ma());
2223        let size = abi::type_bytes(cfg_meta);
2224        let lead = abi::static_out(top_leading.getter(), cfg_meta, size);
2225        let trail = abi::static_out(bottom_trailing.getter(), cfg_meta, size);
2226        let (tail, meta, wt) = scroll_transition_tail(axis);
2227        let mut ints = vec![lead.as_ptr() as u64, trail.as_ptr() as u64];
2228        ints.extend_from_slice(&tail);
2229        generic1_modifier_ints(
2230            resolve::scroll_transition_ends_fn(),
2231            resolve::scroll_transition_ends_oret(),
2232            view,
2233            meta,
2234            wt,
2235            &ints,
2236            [0.0; 4],
2237        )
2238    }
2239}
2240
2241/// Apply `._trait(_:_:)` with a one-byte `Value`.
2242///
2243/// `Value` is an associated type, so the callee cannot know its layout and takes
2244/// it by address however small it is — a `Bool` is a pointer to one byte, not a
2245/// register.
2246unsafe fn bool_trait(
2247    key_ma: *const c_void,
2248    key_wt: *const c_void,
2249    view: &ViewHandle,
2250    value: bool,
2251) -> ViewHandle {
2252    unsafe {
2253        let meta = abi::metadata0(key_ma);
2254        let byte = Box::leak(Box::new(u8::from(value))) as *mut u8;
2255        generic1_modifier_ints(
2256            resolve::view_trait_fn(),
2257            resolve::view_trait_oret(),
2258            view,
2259            meta,
2260            key_wt,
2261            &[meta as u64, byte as u64],
2262            [0.0; 4],
2263        )
2264    }
2265}
2266
2267/// Apply `.selectionDisabled(_:)`.
2268///
2269/// The public spelling is `@_alwaysEmitIntoClient`, so it has no symbol of its
2270/// own — the compiler emits `_trait(IsSelectionEnabledTraitKey.self, !isDisabled)`
2271/// at every call site, and that is exactly what this calls. The `for:_:` overload
2272/// that *does* export a symbol is SPI the compiler will not resolve, so binding
2273/// it would have produced something no Swift program can be written against.
2274pub fn selection_disabled(view: &ViewHandle, disabled: bool) -> ViewHandle {
2275    unsafe {
2276        bool_trait(
2277            resolve::is_selection_enabled_key_ma(),
2278            resolve::is_selection_enabled_key_wt(),
2279            view,
2280            !disabled,
2281        )
2282    }
2283}
2284
2285/// `(Anchor<CGRect>) -> K.Value` — hand back the Rust callback's answer.
2286///
2287/// `K.Value` is an associated type, so it returns through x8 whatever the
2288/// concrete type is; the anchor arrives in x0 and is not read here, since
2289/// nothing outside SwiftUI can resolve one without a layout pass.
2290unsafe extern "C" fn emit_anchor_value(out: *mut u8, payload: *mut c_void) {
2291    unsafe {
2292        let f = boxed_ref::<dyn Fn() -> i64>(payload);
2293        *(out as *mut i64) = f();
2294    }
2295}
2296
2297/// `(inout K.Value, Anchor<CGRect>) -> ()` — let the callback rewrite the value.
2298unsafe extern "C" fn fold_anchor_value(value: *const u8, _anchor: *const u8, payload: *mut c_void) {
2299    unsafe {
2300        let f = boxed_ref::<dyn Fn(i64) -> i64>(payload);
2301        let slot = value as *mut i64;
2302        *slot = f(*slot);
2303    }
2304}
2305
2306/// The pieces `anchorPreference` and `transformAnchorPreference` share.
2307///
2308/// Two generic parameters: the anchor's value type, which carries no
2309/// constraint, and the key, which carries `PreferenceKey`. So the tail is three
2310/// metadata arguments and only *two* witness tables — an unconstrained generic
2311/// parameter contributes a type but no conformance.
2312unsafe fn anchor_preference_parts() -> (*const c_void, *const c_void, *const c_void, u64) {
2313    unsafe {
2314        let key_meta = resolve::spacer_meta();
2315        let key_wt = crate::conformance::preference_key().witness_table();
2316        let rect_meta = crate::type_by_name!(swift_type!(objc struct CGRect).as_bytes());
2317        assert!(!rect_meta.is_null(), "CGRect metadata");
2318        let source: u64;
2319        core::arch::asm!("blr {f}", f = in(reg) resolve::anchor_source_bounds(),
2320            lateout("x0") source,
2321            lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
2322            lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x8") _,
2323            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
2324            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
2325            lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
2326            clobber_abi("C"),
2327        );
2328        (key_meta, key_wt, rect_meta, source)
2329    }
2330}
2331
2332/// Call one of the two anchor-preference modifiers.
2333unsafe fn anchor_modifier(
2334    func: *const c_void,
2335    oret: *const c_void,
2336    view: &ViewHandle,
2337    closure: [u64; 2],
2338) -> ViewHandle {
2339    unsafe {
2340        let (key_meta, key_wt, rect_meta, source) = anchor_preference_parts();
2341        let av = view.meta;
2342        let aw = view.wt;
2343        let subst = [av, rect_meta, key_meta, aw, key_wt];
2344        let (meta, wt, size) = opaque_result_type(oret, &subst);
2345        let ints = [
2346            key_meta as u64,
2347            source,
2348            closure[0],
2349            closure[1],
2350            av as u64,
2351            rect_meta as u64,
2352            key_meta as u64,
2353            aw as u64,
2354            key_wt as u64,
2355        ];
2356        let r = abi::call_modifier_generic(func, view.value_ptr(), &ints, [0.0; 4], size);
2357        wrap_owned(r, meta, wt)
2358    }
2359}
2360
2361/// Apply `.anchorPreference(key:value:transform:)` with a synthesized key.
2362///
2363/// The value is `Anchor<CGRect>.Source.bounds` and the key's `Value` is `Int`,
2364/// supplied by [`crate::conformance`] rather than by any Swift declaration.
2365pub fn anchor_preference(view: &ViewHandle, transform: impl Fn() -> i64 + 'static) -> ViewHandle {
2366    unsafe {
2367        let ctx = Box::leak(Box::new(abi::ClosureCtx::new(
2368            emit_anchor_value,
2369            boxed_ctx(Box::new(transform) as Box<dyn Fn() -> i64>),
2370        ))) as *mut abi::ClosureCtx;
2371        anchor_modifier(
2372            resolve::anchor_preference_fn(),
2373            resolve::anchor_preference_oret(),
2374            view,
2375            [abi::swift_closure_thunk as *const () as u64, ctx as u64],
2376        )
2377    }
2378}
2379
2380/// Apply `.transformAnchorPreference(key:value:transform:)`.
2381///
2382/// The same shape, except the transform takes the running value `inout` — so
2383/// x0 is a pointer to write back through rather than a result to produce.
2384pub fn transform_anchor_preference(view: &ViewHandle, transform: impl Fn(i64) -> i64 + 'static) -> ViewHandle {
2385    unsafe {
2386        anchor_modifier(
2387            resolve::transform_anchor_preference_fn(),
2388            resolve::transform_anchor_preference_oret(),
2389            view,
2390            pair_words(fold_anchor_value, boxed_ctx(Box::new(transform) as Box<dyn Fn(i64) -> i64>)),
2391        )
2392    }
2393}
2394
2395swift_statics! {
2396    /// How eagerly SwiftUI honours a default focus request.
2397    pub enum DefaultFocusPriority {
2398        Automatic = c"$s7SwiftUI30DefaultFocusEvaluationPriorityV9automaticACvgZ",
2399        UserInitiated = c"$s7SwiftUI30DefaultFocusEvaluationPriorityV13userInitiatedACvgZ",
2400    }
2401}
2402
2403/// Apply `.defaultFocus(_:_:priority:)` with `A == Bool`.
2404///
2405/// Three arguments, all by address for the same reason: `FocusState<A>.Binding`
2406/// and the value are generic over `A`, and the priority is resilient. Where
2407/// [`focused`] can hand its concrete `FocusState<Bool>.Binding` over in three
2408/// registers, a generic one cannot travel that way at all.
2409pub fn default_focus(
2410    view: &ViewHandle,
2411    value: bool,
2412    priority: DefaultFocusPriority,
2413) -> ViewHandle {
2414    unsafe {
2415        let b = focus_binding(
2416            mangle::bound(&swift_type!(SwiftUI.FocusState), &[mangle::BOOL]).as_bytes(),
2417            resolve::focus_state_bool_init(),
2418            resolve::focus_state_projected(),
2419        );
2420        let mut binding = [0u8; 24];
2421        for (i, w) in b.iter().enumerate() {
2422            binding[i * 8..i * 8 + 8].copy_from_slice(&w.to_le_bytes());
2423        }
2424        let mut flag = u8::from(value);
2425        let pm = abi::metadata0(resolve::default_focus_priority_ma());
2426        let p = resilient_static(pm, priority.getter());
2427        generic1_modifier_ints(
2428            resolve::default_focus_fn(),
2429            resolve::default_focus_oret(),
2430            view,
2431            resolve::bool_meta(),
2432            resolve::bool_hashable_wt(),
2433            &[
2434                binding.as_ptr() as u64,
2435                &mut flag as *mut u8 as u64,
2436                p.as_ptr() as u64,
2437            ],
2438            [0.0; 4],
2439        )
2440    }
2441}
2442
2443/// Apply `.textRenderer(_:)` with a runtime-synthesized renderer.
2444///
2445/// The public spelling is `@_alwaysEmitIntoClient` and inlines to
2446/// `modifier(_TextRendererViewModifier(renderer:))`, so that is what this
2447/// builds — the modifier value directly, with no symbol to call.
2448pub fn text_renderer(view: &ViewHandle) -> ViewHandle {
2449    unsafe {
2450        let renderer = abi::metadata0(resolve::edge_insets_ma());
2451        let wt = crate::conformance::text_renderer().witness_table();
2452        let modifier =
2453            abi::generic_metadata2(resolve::text_renderer_modifier_ma(), renderer, wt);
2454        // The modifier's one stored property is the renderer, and an all-zero
2455        // `EdgeInsets` is `EdgeInsets()`.
2456        let bytes = vec![0u8; abi::type_bytes(modifier)];
2457        apply_modifier(view, modifier, &bytes)
2458    }
2459}
2460
2461/// Apply `.attributedTextFormattingDefinition(_:)` with `AttributeScopes.SwiftUIAttributes`.
2462///
2463/// The overload taking an `AttributeScope` *type* rather than a definition
2464/// value, so the whole argument is a metatype. The conformance has no exported
2465/// witness table and comes from a runtime lookup.
2466pub fn attributed_text_formatting_definition(view: &ViewHandle) -> ViewHandle {
2467    unsafe {
2468        let meta = abi::metadata0(resolve::swiftui_attributes_ma());
2469        let wt = abi::get_view_wt_for_proto(meta, resolve::attribute_scope_proto());
2470        assert!(!wt.is_null(), "SwiftUIAttributes: AttributeScope");
2471        generic1_modifier_ints(
2472            resolve::attributed_formatting_fn(),
2473            resolve::attributed_formatting_oret(),
2474            view,
2475            meta,
2476            wt,
2477            &[meta as u64],
2478            [0.0; 4],
2479        )
2480    }
2481}
2482
2483swift_statics! {
2484    /// How a view's content changes when its value does.
2485    pub enum ContentTransition {
2486        Identity = c"$s7SwiftUI17ContentTransitionV8identityACvgZ",
2487        Opacity = c"$s7SwiftUI17ContentTransitionV7opacityACvgZ",
2488        Interpolate = c"$s7SwiftUI17ContentTransitionV11interpolateACvgZ",
2489    }
2490}
2491
2492/// `\EnvironmentValues.contentTransition`, built once.
2493fn content_transition_key_path() -> u64 {
2494    use std::sync::atomic::{AtomicU64, Ordering};
2495    static CACHE: AtomicU64 = AtomicU64::new(0);
2496    static mut SLOT: crate::keypath::Slot = crate::keypath::Slot::new();
2497    let hit = CACHE.load(Ordering::Relaxed);
2498    if hit != 0 {
2499        return hit;
2500    }
2501    unsafe {
2502        let kp = crate::keypath::build(
2503            crate::static_mut(&raw mut SLOT),
2504            crate::keypath::Property {
2505                getter: resolve::env_content_transition_getter(),
2506                setter: resolve::env_content_transition_setter(),
2507                descriptor: resolve::env_content_transition_descriptor(),
2508                root: swift_type!(SwiftUI.EnvironmentValues).as_bytes(),
2509                value: swift_type!(SwiftUI.ContentTransition).as_bytes(),
2510            },
2511            crate::keypath::next_thunks(),
2512        );
2513        CACHE.store(kp, Ordering::Relaxed);
2514        kp
2515    }
2516}
2517
2518/// Apply `.contentTransition(_:)`.
2519///
2520/// The public spelling is `@_alwaysEmitIntoClient` and inlines to
2521/// `environment(\.contentTransition, transition)`, so the key path is built at
2522/// run time — see [`crate::keypath`] — and handed to the exported
2523/// `environment(_:_:)`.
2524pub fn content_transition(view: &ViewHandle, transition: ContentTransition) -> ViewHandle {
2525    unsafe {
2526        let meta = abi::metadata0(resolve::content_transition_ma());
2527        let value = resilient_static(meta, transition.getter());
2528        generic1_modifier_ints(
2529            resolve::environment_fn(),
2530            resolve::environment_oret(),
2531            view,
2532            meta,
2533            core::ptr::null(),
2534            &[content_transition_key_path(), value.as_ptr() as u64],
2535            [0.0; 4],
2536        )
2537    }
2538}
2539
2540/// `(A) -> Animation?` — hand back the caller's choice.
2541///
2542/// `Animation` is one word and so is `Animation?`, whose `nil` is a null
2543/// reference rather than a separate tag — so the result goes back in x0 and the
2544/// phase in x0 is simply not read.
2545///
2546/// A closure returns its result at `+1`, and `Animation` is a reference. Handing
2547/// back the borrowed one renders correctly and then dies later, when SwiftUI
2548/// releases a reference this never took.
2549unsafe extern "C" fn emit_phase_animation(payload: *mut c_void, _arg: u64) -> u64 {
2550    if !payload.is_null() {
2551        unsafe { swift_runtime_sys::RuntimeRaw::swift_retain(payload) };
2552    }
2553    payload as u64
2554}
2555
2556/// Apply `.phaseAnimator(_:content:animation:)` with `Phase == Int`.
2557///
2558/// Three generic parameters beyond `Self`: the phase, the sequence of phases and
2559/// the content view. `Array<Int>` is the sequence, but the parameter is a bare
2560/// generic one, so even an array — normally a single loadable word — travels by
2561/// address here.
2562pub fn phase_animator(
2563    view: &ViewHandle,
2564    phases: &[i64],
2565    content: &ViewHandle,
2566    animated: bool,
2567) -> ViewHandle {
2568    unsafe {
2569        let int_meta = resolve::int_meta();
2570        let mut bytes = Vec::with_capacity(phases.len() * 8);
2571        for p in phases {
2572            bytes.extend_from_slice(&p.to_le_bytes());
2573        }
2574        let array = abi::swift_array(int_meta, phases.len(), &bytes);
2575        let array_meta = abi::resolve_type_by_mangled_name(mangle::array(mangle::INT).as_bytes());
2576        assert!(!array_meta.is_null(), "Array<Int> metadata");
2577        let seq_wt = abi::get_view_wt_for_proto(array_meta, resolve::sequence_proto());
2578        assert!(!seq_wt.is_null(), "Array<Int>: Sequence");
2579        let eq_wt = abi::get_view_wt_for_proto(int_meta, resolve::equatable_proto());
2580        assert!(!eq_wt.is_null(), "Int: Equatable");
2581
2582        let anim = if animated {
2583            let meta = abi::metadata0(resolve::animation_ma());
2584            frozen_static(meta, c"$s7SwiftUI9AnimationV7defaultACvgZ")
2585        } else {
2586            0
2587        };
2588        let a = word_closure(emit_phase_animation, anim as *mut c_void);
2589
2590        // `Self` is the receiver; the trailing `Content` is what the content
2591        // closure emits, and `view_words` emits an `AnyView`.
2592        let av = content.meta;
2593        let aw = content.wt;
2594        let subst = [view.meta, int_meta, array_meta, av, view.wt, eq_wt, seq_wt, aw];
2595        let (meta, wt, size) = opaque_result_type(resolve::phase_animator_oret(), &subst);
2596        let mut ints = vec![&array as *const u64 as u64];
2597        ints.extend_from_slice(&view_words(content));
2598        ints.extend_from_slice(&a);
2599        ints.extend_from_slice(&[view.meta as u64, int_meta as u64, array_meta as u64, av as u64]);
2600        ints.extend_from_slice(&[view.wt as u64, eq_wt as u64, seq_wt as u64, aw as u64]);
2601        let r = abi::call_modifier_generic(resolve::phase_animator_fn(), view.value_ptr(), &ints, [0.0; 4], size);
2602        wrap_owned(r, meta, wt)
2603    }
2604}
2605
2606/// Apply `.textInputSuggestions(_:content:)` with `[EdgeInsets]`.
2607///
2608/// The element has to be `Identifiable`, which no public SwiftUI value type is —
2609/// so the conformance is synthesized onto `EdgeInsets` at run time. Four witness
2610/// tables follow the three metadata arguments: `Self: View`, the collection's
2611/// `RandomAccessCollection`, the content's `View`, and the *associated*
2612/// conformance `A.Element: Identifiable`.
2613pub fn text_input_suggestions(
2614    view: &ViewHandle,
2615    count: usize,
2616    content: &ViewHandle,
2617) -> ViewHandle {
2618    unsafe {
2619        let elem = abi::metadata0(resolve::edge_insets_ma());
2620        let stride = abi::type_bytes(elem);
2621        let bytes = vec![0u8; stride * count];
2622        let array = abi::swift_array(elem, count, &bytes);
2623        let array_meta =
2624            crate::type_by_name!(swift_type!([SwiftUI.EdgeInsets]).as_bytes());
2625        assert!(!array_meta.is_null(), "[EdgeInsets] metadata");
2626        let rac = abi::get_view_wt_for_proto(array_meta, resolve::random_access_collection_proto());
2627        assert!(!rac.is_null(), "[EdgeInsets]: RandomAccessCollection");
2628        let ident = crate::conformance::identifiable().witness_table();
2629
2630        // `Self` is the concrete receiver; the trailing type is the content
2631        // the closure emits, which `view_words` erases to an `AnyView`.
2632        let av = content.meta;
2633        let aw = content.wt;
2634        let subst = [view.meta, array_meta, av, view.wt, rac, aw, ident];
2635        let (meta, wt, size) = opaque_result_type(resolve::text_input_suggestions_oret(), &subst);
2636        let mut ints = vec![&array as *const u64 as u64];
2637        ints.extend_from_slice(&view_words(content));
2638        ints.extend_from_slice(&[view.meta as u64, array_meta as u64, av as u64]);
2639        ints.extend_from_slice(&[view.wt as u64, rac as u64, aw as u64, ident as u64]);
2640        let r = abi::call_modifier_generic(resolve::text_input_suggestions_fn(), view.value_ptr(), &ints, [0.0; 4], size);
2641        wrap_owned(r, meta, wt)
2642    }
2643}
2644
2645/// `([A.ID]) -> B` — hand back the container's items.
2646unsafe extern "C" fn emit_drag_items(out: *mut u8, payload: *mut c_void) {
2647    unsafe { *(out as *mut u64) = payload as u64 };
2648}
2649
2650/// Apply `.dragContainer(for:in:_:)` with `Item == String`.
2651///
2652/// The item has to be `Transferable` *and* `Identifiable`. `String` has the
2653/// first already and the second is synthesized onto it — see
2654/// [`crate::conformance::string_identifiable`]. `A.ID: Sendable` adds no
2655/// argument: `Sendable` is a marker protocol, so it carries no witness table.
2656pub fn drag_container(view: &ViewHandle, items: &[&str]) -> ViewHandle {
2657    unsafe {
2658        let item = resolve::string_meta();
2659        let transferable = abi::get_view_wt_for_proto(item, resolve::transferable_proto());
2660        assert!(!transferable.is_null(), "String: Transferable");
2661        let identifiable = crate::conformance::string_identifiable().witness_table();
2662        let array = swift_string_array(items);
2663        let array_meta = abi::resolve_type_by_mangled_name(mangle::array(mangle::STRING).as_bytes());
2664        assert!(!array_meta.is_null(), "[String] metadata");
2665        let collection = abi::get_view_wt_for_proto(array_meta, resolve::collection_proto());
2666        assert!(!collection.is_null(), "[String]: Collection");
2667
2668        // `Namespace.ID?` holding `.none`.
2669        let ns = abi::metadata0(resolve::namespace_id_ma());
2670        let opt = crate::type_by_name!(swift_type!(opt SwiftUI.Namespace.ID).as_bytes());
2671        assert!(!opt.is_null(), "Optional<Namespace.ID>");
2672        let mut none = vec![0u8; abi::type_bytes(opt)];
2673        abi::store_optional_tag(&mut none, ns, false);
2674        let none_words = optional_words(ns, opt, &[], false);
2675
2676        let ctx = Box::leak(Box::new(abi::ClosureCtx::new(
2677            emit_drag_items,
2678            array as *mut c_void,
2679        ))) as *mut abi::ClosureCtx;
2680
2681        let av = view.meta;
2682        let aw = view.wt;
2683        let subst = [av, item, array_meta, aw, transferable, identifiable, collection];
2684        let (meta, wt, size) = opaque_result_type(resolve::drag_container_oret(), &subst);
2685        let mut ints = vec![item as u64];
2686        ints.extend_from_slice(&none_words);
2687        ints.push(abi::swift_closure_thunk as *const () as u64);
2688        ints.push(ctx as u64);
2689        ints.extend_from_slice(&[av as u64, item as u64, array_meta as u64]);
2690        ints.extend_from_slice(&[aw as u64, transferable as u64, identifiable as u64, collection as u64]);
2691        let r = abi::call_modifier_generic(resolve::drag_container_fn(), view.value_ptr(), &ints, [0.0; 4], size);
2692        wrap_owned(r, meta, wt)
2693    }
2694}
2695
2696/// `(P.Output) -> ()` — an associated type, so the value arrives by address.
2697pub(crate) unsafe extern "C" fn saw_published_int(value: *const u8, payload: *mut c_void) {
2698    unsafe {
2699        let f = boxed_ref::<dyn Fn(i64)>(payload);
2700        f(*(value as *const i64));
2701    }
2702}
2703
2704/// Apply `.onReceive(_:perform:)` with a `Combine.Empty<Int, Never>`.
2705pub fn on_receive(view: &ViewHandle, complete_immediately: bool, perform: impl Fn(i64) + 'static) -> ViewHandle {
2706    unsafe {
2707        let publisher = abi::resolve_type_by_mangled_name(mangle::bound("7Combine5EmptyV", &[mangle::INT, mangle::NEVER]).as_bytes());
2708        assert!(!publisher.is_null(), "Empty<Int, Never> metadata");
2709        let wt = abi::get_view_wt_for_proto(publisher, resolve::publisher_proto());
2710        assert!(!wt.is_null(), "Empty<Int, Never>: Publisher");
2711        let value = vec![u8::from(complete_immediately); abi::type_bytes(publisher).max(1)];
2712        let ctx = Box::leak(Box::new(abi::ValueClosureCtx::new(
2713            saw_published_int,
2714            boxed_ctx(Box::new(perform) as Box<dyn Fn(i64)>),
2715        ))) as *mut abi::ValueClosureCtx;
2716        generic1_modifier_ints(
2717            resolve::on_receive_fn(),
2718            resolve::on_receive_oret(),
2719            view,
2720            publisher,
2721            wt,
2722            &[
2723                value.as_ptr() as u64,
2724                abi::value_closure_thunk as *const () as u64,
2725                ctx as u64,
2726            ],
2727            [0.0; 4],
2728        )
2729    }
2730}
2731
2732/// [`apply_modifier`] for out-of-crate diagnostics.
2733#[doc(hidden)]
2734pub unsafe fn apply_modifier_for_test(
2735    view: &ViewHandle,
2736    modifier_meta: *const c_void,
2737    bytes: &[u8],
2738) -> ViewHandle {
2739    unsafe { apply_modifier(view, modifier_meta, bytes) }
2740}
2741
2742/// Apply `.focusedValue(_:)` with an `Observable` object.
2743///
2744/// The overload taking a key path needs a property on `FocusedValues`, which
2745/// only a client declares — so this is the other one, generic over
2746/// `AnyObject & Observable`. `AnyObject` is a layout constraint and contributes
2747/// no witness table; only the `Observable` conformance does, and that is
2748/// synthesized, because `Observable` has no requirements to satisfy.
2749pub fn focused_value(view: &ViewHandle, present: bool) -> ViewHandle {
2750    unsafe {
2751        let meta = abi::resolve_type_by_mangled_name(
2752            mangle::Type::Nominal {
2753                module: "Foundation",
2754                path: vec!["JSONDecoder"],
2755                kind: mangle::Kind::Class,
2756            }
2757            .mangle()
2758            .as_bytes(),
2759        );
2760        assert!(!meta.is_null(), "Foundation.JSONDecoder metadata");
2761        let wt = crate::conformance::observable().witness_table();
2762        let object = if present {
2763            let o: u64;
2764            core::arch::asm!("blr {f}", f = in(reg) resolve::json_decoder_init(),
2765                inlateout("x0") meta => o, in("x20") meta,
2766                lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
2767                lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x8") _,
2768                lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
2769                lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
2770                lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
2771            );
2772            o
2773        } else {
2774            // `Optional` of a class reference needs no tag: `nil` is null.
2775            0
2776        };
2777        generic1_modifier_ints(
2778            resolve::focused_value_fn(),
2779            resolve::focused_value_oret(),
2780            view,
2781            meta,
2782            wt,
2783            &[object],
2784            [0.0; 4],
2785        )
2786    }
2787}
2788
2789/// The one `MoveKeyframe` a track is built from, and the track itself.
2790///
2791/// Leaked into statics because the closures Swift keeps have nowhere to store
2792/// them: a `ClosureCtx` carries one payload word, and both of these are values
2793/// several words wide. There is one of each for the process.
2794static FRAME: std::sync::OnceLock<(Vec<u8>, usize)> = std::sync::OnceLock::new();
2795static TRACK: std::sync::OnceLock<(Vec<u8>, usize)> = std::sync::OnceLock::new();
2796
2797/// `() -> MoveKeyframe<Double>` — a generic result, so it goes through x8.
2798unsafe extern "C" fn emit_frame(out: *mut u8, _payload: *mut c_void) {
2799    let (bytes, meta) = FRAME.get().expect("keyframe built before the closure runs");
2800    unsafe { abi::copy_value(out, bytes.as_ptr(), *meta as *const c_void) };
2801}
2802
2803/// `(Double) -> KeyframeTrack<…>` — likewise, and the value in x0 is not read.
2804unsafe extern "C" fn emit_track(out: *mut u8, _payload: *mut c_void) {
2805    let (bytes, meta) = TRACK.get().expect("track built before the closure runs");
2806    unsafe { abi::copy_value(out, bytes.as_ptr(), *meta as *const c_void) };
2807}
2808
2809/// Build `KeyframeTrack<Double, Double, MoveKeyframe<Double>>` holding one
2810/// keyframe at `to`, and return it with its `Keyframes` conformance.
2811///
2812/// Every metadata here comes from a mangled name rather than a `…Ma` accessor.
2813/// `MoveKeyframe` constrains its parameter, so its accessor takes the witness
2814/// table as well — and calling it with one argument does not fail, it poisons
2815/// the metadata cache and the *next* type that mentions `MoveKeyframe` dies
2816/// instead, somewhere unrelated.
2817unsafe fn keyframe_track(to: f64) -> (*const c_void, *const c_void) {
2818    unsafe {
2819        let double = resolve::double_meta();
2820        let frame = abi::resolve_type_by_mangled_name(
2821            mangle::ty("MoveKeyframe")
2822                .of(vec![mangle::Type::Verbatim("Sd".into())])
2823                .mangle()
2824                .as_bytes(),
2825        );
2826        assert!(!frame.is_null(), "MoveKeyframe<Double> metadata");
2827        let frame_wt =
2828            abi::get_view_wt_for_proto(frame, resolve::keyframe_track_content_proto());
2829        assert!(!frame_wt.is_null(), "MoveKeyframe<Double>: KeyframeTrackContent");
2830
2831        let mut at = to;
2832        let mut one = vec![0u8; abi::type_bytes(frame)];
2833        abi::call_out(
2834            resolve::move_keyframe_init(),
2835            abi::ints(&[&mut at as *mut f64 as u64, double as u64]),
2836            [0.0; 4],
2837            frame as u64,
2838            one.as_mut_ptr(),
2839        );
2840        let _ = FRAME.set((one, frame as usize));
2841
2842        let track = abi::resolve_type_by_mangled_name(
2843            mangle::ty("KeyframeTrack")
2844                .of(vec![
2845                    mangle::Type::Verbatim("Sd".into()),
2846                    mangle::Type::Verbatim("Sd".into()),
2847                    mangle::ty("MoveKeyframe").of(vec![mangle::Type::Verbatim("Sd".into())]),
2848                ])
2849                .mangle()
2850                .as_bytes(),
2851        );
2852        assert!(!track.is_null(), "KeyframeTrack metadata");
2853        let track_wt = abi::get_view_wt_for_proto(track, resolve::keyframes_proto());
2854        assert!(!track_wt.is_null(), "KeyframeTrack: Keyframes");
2855
2856        let ctx = Box::leak(Box::new(abi::ClosureCtx::new(emit_frame, core::ptr::null_mut())))
2857            as *mut abi::ClosureCtx;
2858        let mut bytes = vec![0u8; abi::type_bytes(track)];
2859        abi::call_spilled(
2860            resolve::keyframe_track_init(),
2861            &[
2862                abi::swift_closure_thunk as *const () as u64,
2863                ctx as u64,
2864                double as u64,
2865                frame as u64,
2866                frame_wt as u64,
2867            ],
2868            [0.0; 4],
2869            track as u64,
2870            bytes.as_mut_ptr(),
2871        );
2872        let _ = TRACK.set((bytes, track as usize));
2873        (track, track_wt)
2874    }
2875}
2876
2877/// Apply `.keyframeAnimator(initialValue:repeating:content:keyframes:)`.
2878///
2879/// Three generic parameters beyond `Self`: the animated value, the content
2880/// view, and the keyframes. Nothing public conforms to `Keyframes` by itself —
2881/// the conformance belongs to `KeyframeTrack`, which is built here around a
2882/// single `MoveKeyframe`.
2883pub fn keyframe_animator(
2884    view: &ViewHandle,
2885    initial: f64,
2886    to: f64,
2887    repeating: bool,
2888    content: &ViewHandle,
2889) -> ViewHandle {
2890    unsafe {
2891        let (track, track_wt) = keyframe_track(to);
2892        let double = resolve::double_meta();
2893        // `Self` is the concrete receiver; `Content` is whatever the content
2894        // closure emits, and `view_words` emits an `AnyView`. They were the
2895        // same type only while every receiver was erased.
2896        let av = content.meta;
2897        let aw = content.wt;
2898        let subst = [view.meta, double, av, track, view.wt, aw, track_wt];
2899        let (meta, wt, size) = opaque_result_type(resolve::keyframe_animator_oret(), &subst);
2900
2901        let keyframes = Box::leak(Box::new(abi::ClosureCtx::new(
2902            emit_track,
2903            core::ptr::null_mut(),
2904        ))) as *mut abi::ClosureCtx;
2905
2906        let mut start = initial;
2907        let mut ints = vec![&mut start as *mut f64 as u64, u64::from(repeating)];
2908        ints.extend_from_slice(&view_words(content));
2909        ints.extend_from_slice(&[
2910            abi::swift_closure_thunk as *const () as u64,
2911            keyframes as u64,
2912        ]);
2913        ints.extend_from_slice(&[view.meta as u64, double as u64, av as u64, track as u64]);
2914        ints.extend_from_slice(&[view.wt as u64, aw as u64, track_wt as u64]);
2915        let r = abi::call_modifier_generic(resolve::keyframe_animator_fn(), view.value_ptr(), &ints, [0.0; 4], size);
2916        wrap_owned(r, meta, wt)
2917    }
2918}
2919
2920// ── Reading the environment ─────────────────────────────────────────────────
2921
2922/// A borrowed `SwiftUI.EnvironmentValues`, live for the body call that got it.
2923///
2924/// The pointer is SwiftUI's, valid only while the closure that received it is
2925/// running, which is why this borrows rather than owns.
2926#[derive(Clone, Copy)]
2927pub struct EnvironmentValues(*const c_void);
2928
2929impl EnvironmentValues {
2930    /// Read a `Bool` property by its getter symbol.
2931    ///
2932    /// `EnvironmentValues` is resilient, so `self` travels by address in x20
2933    /// and the answer comes back in x0. Every `Bool` property on it has the
2934    /// same shape, so one function covers all of them.
2935    ///
2936    /// # Safety
2937    /// `getter` must name a `(EnvironmentValues) -> Bool` property getter.
2938    pub unsafe fn bool_at(self, getter: *const c_void) -> bool {
2939        unsafe { abi::call_regs(getter, abi::ints(&[]), [0.0; 4], self.0 as u64)[0] & 1 != 0 }
2940    }
2941
2942    /// `\\.accessibilityVoiceOverEnabled`
2943    pub fn voice_over_enabled(self) -> bool {
2944        unsafe { self.bool_at(resolve::env_accessibility_voice_over()) }
2945    }
2946
2947    /// `\\.accessibilitySwitchControlEnabled`
2948    pub fn switch_control_enabled(self) -> bool {
2949        unsafe { self.bool_at(resolve::env_accessibility_switch_control()) }
2950    }
2951
2952    /// Read a key defined here — `env[myKey]`, through the key path.
2953    ///
2954    /// `swift_getAtKeyPath(sret Value, @in Root, KeyPath)`: the result is `sret`
2955    /// so it goes through x8 while the root is in x0. Whatever nothing above has
2956    /// written, this returns the key's `defaultValue` — which SwiftUI gets from
2957    /// the synthesised witness, so the default really is the caller's.
2958    ///
2959    /// The type comes from the key, so this cannot read a `String` key as an
2960    /// `Int`: the buffer is sized by `T`'s metadata and `+1`-owned on return,
2961    /// which is why it is destroyed after the copy out.
2962    pub fn value_for<T: super::KeyValue>(self, key: &super::CustomEnvKey<T>) -> T {
2963        unsafe {
2964            static GET: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
2965            let f = *GET.get_or_init(|| {
2966                libc::dlsym((-2isize) as *mut c_void, c"swift_getAtKeyPath".as_ptr()) as usize
2967            });
2968            assert!(f != 0, "swift_getAtKeyPath");
2969            let mut out = vec![0u8; abi::type_bytes(T::metadata())];
2970            core::arch::asm!("blr {f}", f = in(reg) f,
2971                in("x0") self.0,
2972                in("x1") key.key_path_for_test(),
2973                in("x8") out.as_mut_ptr(),
2974                lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
2975                lateout("x6") _, lateout("x7") _,
2976                lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
2977                lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
2978                lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
2979                clobber_abi("C"),
2980            );
2981            let value = T::read(out.as_ptr());
2982            abi::type_destroy(T::metadata(), out.as_mut_ptr());
2983            value
2984        }
2985    }
2986}
2987
2988type EnvBuilder = Box<dyn Fn(EnvironmentValues) -> ViewHandle>;
2989
2990/// The body SwiftUI calls: the environment arrives as an address, the result
2991/// goes back through the out pointer as an `AnyView`.
2992unsafe extern "C" fn emit_environment_view(out: *mut u8, env: i64, payload: *mut c_void) {
2993    unsafe {
2994        let build = &*(payload as *const EnvBuilder);
2995        let view = build(EnvironmentValues(env as *const c_void));
2996        (out as *mut u64).write(leak_retained(&view));
2997    }
2998}
2999
3000/// A view whose content is a function of the environment.
3001///
3002/// This is what `@Environment` is for, without needing to be a view *type*.
3003/// A Swift view declares stored `Environment<V>` properties and SwiftUI finds
3004/// them by reflecting over the type's field descriptors, populates them, and
3005/// then calls `body` — machinery that only exists for types the compiler
3006/// emitted. `EnvironmentReader` takes the closure instead, so a view built out
3007/// of values can still depend on the environment:
3008///
3009/// ```ignore
3010/// environment_reader(|env| {
3011///     if env.voice_over_enabled() { button() } else { empty() }
3012/// })
3013/// ```
3014///
3015/// The closure is called during layout, every time the environment changes, so
3016/// it is leaked rather than borrowed from the frame that built it.
3017pub fn environment_reader(
3018    builder: impl Fn(EnvironmentValues) -> ViewHandle + 'static,
3019) -> ViewHandle {
3020    unsafe {
3021        let boxed: EnvBuilder = Box::new(builder);
3022        let payload = Box::into_raw(Box::new(boxed)) as *mut c_void;
3023        let ctx = Box::leak(Box::new(abi::IndexClosureCtx::new(emit_environment_view, payload)))
3024            as *mut abi::IndexClosureCtx;
3025
3026        let ty = mangle::ty("EnvironmentReader").of(vec![mangle::ty("AnyView")]).mangle();
3027        let meta = abi::resolve_type_by_mangled_name(ty.as_bytes());
3028        assert!(!meta.is_null(), "EnvironmentReader<AnyView> metadata");
3029
3030        // Through x8, not in registers. `EnvironmentReader<AnyView>` is two
3031        // words and loadable — but the *initialiser* is generic over `A`, and
3032        // from inside it `EnvironmentReader<A>` has unknown layout, so the
3033        // result is address-only however small it turns out to be.
3034        let mut value = vec![0u8; abi::type_bytes(meta)];
3035        abi::call_out(
3036            resolve::environment_reader_init(),
3037            abi::ints(&[abi::index_closure_thunk as *const c_void as u64, ctx as u64]),
3038            [0.0; 4],
3039            meta as u64,
3040            value.as_mut_ptr().cast(),
3041        );
3042        let wt = abi::get_view_wt(meta);
3043        assert!(!wt.is_null(), "EnvironmentReader !: View");
3044        ViewHandle::wrap(value.as_ptr() as *const c_void, meta, wt)
3045    }
3046}
3047
3048#[cfg(test)]
3049mod geometry_tests {
3050    use super::*;
3051
3052    /// The scroll-geometry reader agrees with the live layout.
3053    ///
3054    /// Same shape of check as the pinch readers': a buffer with a distinct
3055    /// value at each offset the metadata reports, and the reader has to return
3056    /// them in the right places. `EdgeInsets` is the one that would go unnoticed
3057    /// if it were wrong, because all four of its fields are plain `CGFloat` —
3058    /// so they get four different numbers.
3059    #[test]
3060    fn scroll_geometry_reader_matches_the_live_layout() {
3061        let &[offset, size, insets, container, visible] =
3062            scroll_geometry_offsets().expect("five stored fields");
3063        let bytes = unsafe { abi::type_bytes(abi::metadata0(resolve::scroll_geometry_ma())) };
3064        let mut buf = vec![0u8; bytes];
3065        let mut put = |at: usize, v: f64| buf[at..at + 8].copy_from_slice(&v.to_le_bytes());
3066        for (base, n) in [(offset, 2), (size, 2), (insets, 4), (container, 2), (visible, 4)] {
3067            for i in 0..n {
3068                put(base + i * 8, (base + i * 8) as f64);
3069            }
3070        }
3071
3072        let g = unsafe { read_scroll_geometry(buf.as_ptr()) };
3073        let f = |o: usize| o as f64;
3074        assert_eq!(g.content_offset, (f(offset), f(offset + 8)));
3075        assert_eq!(g.content_size, (f(size), f(size + 8)));
3076        assert_eq!(
3077            g.content_insets,
3078            (f(insets), f(insets + 8), f(insets + 16), f(insets + 24)),
3079            "top, leading, bottom, trailing — in that order"
3080        );
3081        assert_eq!(g.container_size, (f(container), f(container + 8)));
3082        assert_eq!(
3083            g.visible_rect,
3084            (f(visible), f(visible + 8), f(visible + 16), f(visible + 24)),
3085            "CGRect is origin then size, so x, y, width, height"
3086        );
3087    }
3088}