Skip to main content

swiftui_native/views/
customstyle.rs

1//! The protocols SwiftUI asks a *client* to conform to, conformed to in Rust.
2//!
3//! Five of them live here — `ButtonStyle`, `LabelStyle`, `ViewModifier`,
4//! `Transition` and `CustomAnimation` — and they are together because they are
5//! one idea: a protocol whose whole purpose is that the *caller* implements it,
6//! a conformance synthesised at run time, and the callback landing in a Rust
7//! closure. `Layout` is the same idea in
8//! [`crate::views::customlayout`]; `Shape` in [`crate::views::customshape`],
9//! where it needed a chapter of its own.
10//!
11//! What they have in common beyond that is the witness convention: every one of
12//! these callbacks takes its argument in x0, `self` in x20 and its result
13//! through x8, which no `extern "C"` signature can express — so each goes
14//! through a small assembly thunk. Declaring the result as the first parameter
15//! instead puts it in x0 and the argument in x1, and SwiftUI writes a view into
16//! whatever x0 happened to hold. That is what the thunks are for, and it is
17//! worth knowing that it was found by doing it.
18//!
19//! ```no_run
20//! # use swiftui_native::{prelude::*, views::{self, ButtonConfiguration}};
21//! // Every button below this one, drawn by Rust — including its reaction to
22//! // being pressed, which is why a style is a callback and not a description.
23//! let style = views::button_style_fn(|c: ButtonConfiguration<'_>| {
24//!     let a = if c.is_pressed() { 0.4 } else { 1.0 };
25//!     View::new(c.label()).padding(8.0).background(0.2, 0.6, 1.0, a).corner_radius(6.0).into_handle()
26//! });
27//! let v = View::new(views::button("Press", || {})).custom_button_style(&style);
28//! ```
29//!
30//! # `Body` is `AnyView`
31//!
32//! A Swift style declares `associatedtype Body` and returns `some View`, so
33//! each style has its own body type. A Rust closure cannot: what it returns is
34//! decided at run time, not by its signature. So the associated type is
35//! `AnyView` and the body is erased into one — the same trade the rest of this
36//! crate makes wherever a closure's result crosses into SwiftUI.
37
38use super::{wrap_owned, ViewHandle};
39use crate::abi;
40use crate::conformance::{synthesize, Conformance, Protocol, Witness};
41use crate::resolve;
42use core::ffi::c_void;
43use std::sync::{Mutex, OnceLock};
44
45/// What a button style is told about the button it is drawing.
46pub struct ButtonConfiguration<'a> {
47    raw: *mut c_void,
48    marker: core::marker::PhantomData<&'a ()>,
49}
50
51impl ButtonConfiguration<'_> {
52    /// The button's own label, to place inside whatever the style draws.
53    pub fn label(&self) -> ViewHandle {
54        unsafe { config_view(resolve::button_config_label(), resolve::button_config_label_ma(), self.raw) }
55    }
56
57    /// Whether the button is being pressed right now.
58    ///
59    /// The reason a style is a callback rather than a static description:
60    /// SwiftUI re-runs it as this changes.
61    pub fn is_pressed(&self) -> bool {
62        unsafe { config_flag(resolve::button_config_pressed(), self.raw) }
63    }
64}
65
66/// What a label style is told.
67pub struct LabelConfiguration<'a> {
68    raw: *mut c_void,
69    marker: core::marker::PhantomData<&'a ()>,
70}
71
72impl LabelConfiguration<'_> {
73    /// The label's text.
74    pub fn title(&self) -> ViewHandle {
75        unsafe { config_view(resolve::label_config_title(), resolve::label_config_title_ma(), self.raw) }
76    }
77
78    /// The label's icon.
79    pub fn icon(&self) -> ViewHandle {
80        unsafe { config_view(resolve::label_config_icon(), resolve::label_config_icon_ma(), self.raw) }
81    }
82}
83
84/// Read a view-valued property off a style configuration.
85///
86/// The property's type — `ButtonStyleConfiguration.Label` and friends — is an
87/// opaque wrapper SwiftUI hands back; its `View` witness is looked up rather
88/// than named, because the `…Wl` accessor for it is emitted into the client
89/// and is not something a binding can call.
90///
91/// # Safety
92/// `getter` must be a property getter on the configuration `raw` points at,
93/// and `ma` that property's metadata accessor.
94unsafe fn config_view(
95    getter: *const c_void,
96    ma: *const c_void,
97    raw: *mut c_void,
98) -> ViewHandle {
99    unsafe {
100        let meta = abi::metadata0(ma);
101        assert!(!meta.is_null(), "style configuration property metadata");
102        let wt = abi::get_view_wt(meta);
103        assert!(!wt.is_null(), "{} !: View", crate::diag::type_name(meta));
104        let mut out = vec![0u8; abi::type_bytes(meta)];
105        core::arch::asm!("blr {f}", f = in(reg) getter,
106            in("x8") out.as_mut_ptr(),
107            in("x20") raw,
108            lateout("x0") _, lateout("x1") _, lateout("x2") _, lateout("x3") _,
109            lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
110            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
111            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
112            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
113        );
114        wrap_owned(out, meta, wt)
115    }
116}
117
118/// Read a `Bool` property off a style configuration.
119///
120/// # Safety
121/// As [`config_view`].
122unsafe fn config_flag(getter: *const c_void, raw: *mut c_void) -> bool {
123    unsafe {
124        let out: u64;
125        core::arch::asm!("blr {f}", f = in(reg) getter,
126            in("x20") raw,
127            lateout("x0") out,
128            lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
129            lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x8") _,
130            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
131            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
132            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
133        );
134        out & 1 != 0
135    }
136}
137
138// ── The styles, kept on the Rust side ────────────────────────────────────
139
140/// How a button draws.
141pub trait ButtonStyling: Send + Sync + 'static {
142    /// Draw one button. `configuration` carries its label and whether it is
143    /// pressed.
144    fn make_body(&self, configuration: ButtonConfiguration<'_>) -> ViewHandle;
145}
146
147impl<F> ButtonStyling for F
148where
149    F: Fn(ButtonConfiguration<'_>) -> ViewHandle + Send + Sync + 'static,
150{
151    fn make_body(&self, configuration: ButtonConfiguration<'_>) -> ViewHandle {
152        self(configuration)
153    }
154}
155
156/// How a label draws.
157pub trait LabelStyling: Send + Sync + 'static {
158    /// Draw one label. `configuration` carries its title and its icon.
159    fn make_body(&self, configuration: LabelConfiguration<'_>) -> ViewHandle;
160}
161
162impl<F> LabelStyling for F
163where
164    F: Fn(LabelConfiguration<'_>) -> ViewHandle + Send + Sync + 'static,
165{
166    fn make_body(&self, configuration: LabelConfiguration<'_>) -> ViewHandle {
167        self(configuration)
168    }
169}
170
171/// A registered style, as the one word SwiftUI stores.
172///
173/// The same arrangement as a custom layout: SwiftUI copies a style value
174/// freely, so what it holds is an index into a Rust-side table rather than the
175/// closure, which is not something it could copy.
176pub struct CustomButtonStyle {
177    index: i64,
178}
179
180/// As [`CustomButtonStyle`], for labels.
181pub struct CustomLabelStyle {
182    index: i64,
183}
184
185fn button_registry() -> &'static Mutex<Vec<&'static dyn ButtonStyling>> {
186    static R: OnceLock<Mutex<Vec<&'static dyn ButtonStyling>>> = OnceLock::new();
187    R.get_or_init(|| Mutex::new(Vec::new()))
188}
189
190fn label_registry() -> &'static Mutex<Vec<&'static dyn LabelStyling>> {
191    static R: OnceLock<Mutex<Vec<&'static dyn LabelStyling>>> = OnceLock::new();
192    R.get_or_init(|| Mutex::new(Vec::new()))
193}
194
195/// Register a button style. A closure taking a [`ButtonConfiguration`] is one.
196pub fn button_style_fn<S: ButtonStyling>(style: S) -> CustomButtonStyle {
197    let leaked: &'static dyn ButtonStyling = Box::leak(Box::new(style));
198    let mut r = button_registry().lock().expect("button style registry");
199    r.push(leaked);
200    CustomButtonStyle { index: r.len() as i64 - 1 }
201}
202
203/// Register a label style.
204pub fn label_style_fn<S: LabelStyling>(style: S) -> CustomLabelStyle {
205    let leaked: &'static dyn LabelStyling = Box::leak(Box::new(style));
206    let mut r = label_registry().lock().expect("label style registry");
207    r.push(leaked);
208    CustomLabelStyle { index: r.len() as i64 - 1 }
209}
210
211// ── The witnesses SwiftUI calls ──────────────────────────────────────────
212
213// `makeBody(configuration:)` receives its indirect result in **x8**, the
214// configuration in x0 and `self` in x20 — the witness-method convention. An
215// `extern "C"` function cannot name x8, so a thunk shuffles the three into the
216// first three argument registers before tail-calling. Declaring the result as
217// the first parameter instead put it in x0 and the configuration in x1, and
218// SwiftUI wrote a view into whatever x0 happened to hold.
219core::arch::global_asm!(
220    ".p2align 2",
221    ".globl _rswift_button_style_thunk",
222    "_rswift_button_style_thunk:",
223    "mov x2, x20",
224    "mov x1, x0",
225    "mov x0, x8",
226    "b _rswift_button_make_body",
227    ".p2align 2",
228    ".globl _rswift_label_style_thunk",
229    "_rswift_label_style_thunk:",
230    "mov x2, x20",
231    "mov x1, x0",
232    "mov x0, x8",
233    "b _rswift_label_make_body",
234);
235
236unsafe extern "C" {
237    #[link_name = "rswift_button_style_thunk"]
238    fn button_style_thunk();
239    #[link_name = "rswift_label_style_thunk"]
240    fn label_style_thunk();
241}
242
243/// `makeBody(configuration:) -> Body`, with `Body == AnyView`.
244///
245/// # Safety
246/// Reached through `rswift_button_style_thunk`, which supplies the three
247/// arguments in order.
248#[unsafe(no_mangle)]
249unsafe extern "C" fn rswift_button_make_body(
250    out: *mut u8,
251    configuration: *mut c_void,
252    this: *const u8,
253) {
254    let index = unsafe { *(this as *const i64) } as usize;
255    let style = {
256        let r = button_registry().lock().expect("button style registry");
257        r.get(index).copied().expect("button style index out of range")
258    };
259    let body = style.make_body(ButtonConfiguration {
260        raw: configuration,
261        marker: core::marker::PhantomData,
262    });
263    // `Body` is `AnyView`, so the result is one erased word.
264    unsafe { *(out as *mut u64) = body.into_anyview_word() };
265}
266
267/// As [`rswift_button_make_body`], for a label style.
268///
269/// # Safety
270/// As above.
271#[unsafe(no_mangle)]
272unsafe extern "C" fn rswift_label_make_body(
273    out: *mut u8,
274    configuration: *mut c_void,
275    this: *const u8,
276) {
277    let index = unsafe { *(this as *const i64) } as usize;
278    let style = {
279        let r = label_registry().lock().expect("label style registry");
280        r.get(index).copied().expect("label style index out of range")
281    };
282    let body = style.make_body(LabelConfiguration {
283        raw: configuration,
284        marker: core::marker::PhantomData,
285    });
286    unsafe { *(out as *mut u64) = body.into_anyview_word() };
287}
288
289/// The carrier type a style value *is*: one `Int`, the style's index.
290fn button_carrier() -> (*const c_void, *const c_void) {
291    static C: OnceLock<(usize, usize)> = OnceLock::new();
292    let (d, m) = *C.get_or_init(|| unsafe {
293        let (d, m) =
294            crate::state::emit_int_carrier(b"RustButtonStyle\0", b"6rswift15RustButtonStyleV\0");
295        (d as usize, m as usize)
296    });
297    (d as *const c_void, m as *const c_void)
298}
299
300/// As [`button_carrier`], for label styles. A separate type, because a
301/// conformance is registered per `(type, protocol)` and one type conforming to
302/// both would make `buttonStyle(_:)` accept a label style.
303fn label_carrier() -> (*const c_void, *const c_void) {
304    static C: OnceLock<(usize, usize)> = OnceLock::new();
305    let (d, m) = *C.get_or_init(|| unsafe {
306        let (d, m) =
307            crate::state::emit_int_carrier(b"RustLabelStyle\0", b"6rswift14RustLabelStyleV\0");
308        (d as usize, m as usize)
309    });
310    (d as *const c_void, m as *const c_void)
311}
312
313/// A style conformance: the associated `Body`, its `View` witness, `makeBody`.
314///
315/// The three requirements are in that order and none has a default, which is
316/// why both protocols read the same here.
317///
318/// # Safety
319/// `make_body` must have the witness-method signature above.
320unsafe fn style_conformance(
321    protocol: &'static core::ffi::CStr,
322    descriptor: *const c_void,
323    make_body: *const c_void,
324) -> Conformance {
325    unsafe {
326        let protocol = Protocol::from_symbol(protocol);
327        assert_eq!(protocol.requirement_count(), 3, "the style protocol changed shape");
328        synthesize(
329            &protocol,
330            descriptor,
331            &[
332                Witness::Metadata(resolve::anyview_wt()),
333                Witness::Metadata(resolve::anyview_meta()),
334                Witness::Function(make_body),
335            ],
336        )
337    }
338}
339
340fn button_conformance() -> &'static Conformance {
341    static C: OnceLock<Conformance> = OnceLock::new();
342    C.get_or_init(|| unsafe {
343        style_conformance(
344            c"$s7SwiftUI11ButtonStyleMp",
345            button_carrier().0,
346            button_style_thunk as *const c_void,
347        )
348    })
349}
350
351fn label_conformance() -> &'static Conformance {
352    static C: OnceLock<Conformance> = OnceLock::new();
353    C.get_or_init(|| unsafe {
354        style_conformance(
355            c"$s7SwiftUI10LabelStyleMp",
356            label_carrier().0,
357            label_style_thunk as *const c_void,
358        )
359    })
360}
361
362/// Apply a style, whichever of the two it is.
363///
364/// `buttonStyle(_:)` and `labelStyle(_:)` take the same arguments in the same
365/// registers and differ only in the protocol they constrain to, so one body
366/// serves both.
367///
368/// # Safety
369/// `f` and `oret` must be a matching modifier and opaque-result descriptor,
370/// and `conformance` a conformance to that modifier's protocol.
371unsafe fn apply_style(
372    view: &ViewHandle,
373    index: i64,
374    conformance: &Conformance,
375    carrier: (*const c_void, *const c_void),
376    f: *const c_void,
377    oret: *const c_void,
378) -> ViewHandle {
379    unsafe {
380        let meta = carrier.1;
381        let mut value = vec![0u8; abi::type_bytes(meta)];
382        value[..8].copy_from_slice(&index.to_le_bytes());
383
384        let subst = [view.meta, meta, view.wt, conformance.witness_table()];
385        let (out_meta, out_wt, size) = super::opaque_result_type(oret, &subst);
386        let mut result = vec![0u8; size];
387        let this = view.value_ptr();
388        // `(sret, @in style, Self.Type, S.Type, Self: View WT,
389        //   S: ButtonStyle WT, swiftself self)`
390        core::arch::asm!("blr {f}", f = in(reg) f,
391            in("x0") value.as_mut_ptr(),
392            in("x1") view.meta,
393            in("x2") meta,
394            in("x3") view.wt,
395            in("x4") conformance.witness_table(),
396            in("x8") result.as_mut_ptr(),
397            in("x20") this,
398            lateout("x5") _, lateout("x6") _, lateout("x7") _,
399            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
400            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
401            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
402        );
403        wrap_owned(result, out_meta, out_wt)
404    }
405}
406
407/// `.buttonStyle(_:)` with a style written in Rust.
408pub fn custom_button_style(view: &ViewHandle, style: &CustomButtonStyle) -> ViewHandle {
409    unsafe {
410        apply_style(
411            view,
412            style.index,
413            button_conformance(),
414            button_carrier(),
415            resolve::button_style_custom_fn(),
416            resolve::button_style_custom_oret(),
417        )
418    }
419}
420
421/// `.labelStyle(_:)` with a style written in Rust.
422pub fn custom_label_style(view: &ViewHandle, style: &CustomLabelStyle) -> ViewHandle {
423    unsafe {
424        apply_style(
425            view,
426            style.index,
427            label_conformance(),
428            label_carrier(),
429            resolve::label_style_fn(),
430            resolve::label_style_oret(),
431        )
432    }
433}
434
435// ── A `ViewModifier` written in Rust ──
436//
437// The third protocol in this file, and the same shape as the first two: SwiftUI
438// asks a *client* to conform, the conformance is synthesised, and the callback
439// lands in a Rust closure. What makes it worth having beyond
440// [`custom_button_style`] is reach — a `ViewModifier` applies to *any* view, not
441// only to buttons, so it is how a Rust caller packages "my card style" once and
442// uses it everywhere.
443//
444// `body(content:)`'s argument is a `_ViewModifier_Content<Self>`: SwiftUI's stand-in
445// for "whatever this modifier was applied to". It is a real `View`, so the
446// closure gets it as an ordinary [`ViewHandle`] and can wrap it.
447
448/// How a view modifier draws.
449pub trait Modifying: Send + Sync + 'static {
450    /// Draw one modified view. `content` is whatever the modifier was applied to.
451    fn body(&self, content: ViewHandle) -> ViewHandle;
452}
453
454impl<F> Modifying for F
455where
456    F: Fn(ViewHandle) -> ViewHandle + Send + Sync + 'static,
457{
458    fn body(&self, content: ViewHandle) -> ViewHandle {
459        self(content)
460    }
461}
462
463/// A registered modifier, as the one word SwiftUI stores.
464pub struct CustomModifier {
465    index: i64,
466}
467
468fn modifier_registry() -> &'static Mutex<Vec<&'static dyn Modifying>> {
469    static R: OnceLock<Mutex<Vec<&'static dyn Modifying>>> = OnceLock::new();
470    R.get_or_init(|| Mutex::new(Vec::new()))
471}
472
473/// Register a view modifier. A closure taking the content is one.
474pub fn modifier_fn<M: Modifying>(modifier: M) -> CustomModifier {
475    let leaked: &'static dyn Modifying = Box::leak(Box::new(modifier));
476    let mut r = modifier_registry().lock().expect("modifier registry");
477    r.push(leaked);
478    CustomModifier { index: r.len() as i64 - 1 }
479}
480
481// `body(content:)` has exactly `makeBody(configuration:)`'s convention — result
482// in x8, argument in x0, `self` in x20 — so the same shuffle serves it.
483core::arch::global_asm!(
484    ".p2align 2",
485    ".globl _rswift_view_modifier_thunk",
486    "_rswift_view_modifier_thunk:",
487    "mov x2, x20",
488    "mov x1, x0",
489    "mov x0, x8",
490    "b _rswift_view_modifier_body",
491);
492
493unsafe extern "C" {
494    #[link_name = "rswift_view_modifier_thunk"]
495    fn view_modifier_thunk();
496}
497
498/// The content's metadata and `View` conformance, per registered modifier.
499///
500/// `_ViewModifier_Content<Modifier>` is generic over the modifier and
501/// constrained to `ViewModifier`, so building its metadata needs the carrier's
502/// metadata *and* the synthesised conformance — which is why this is looked up
503/// once the conformance exists rather than at first use.
504static CONTENT_META: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
505static CONTENT_WT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
506
507/// `body(content:) -> Body`, with `Body == AnyView`.
508///
509/// # Safety
510/// Reached through `rswift_view_modifier_thunk`, which supplies the three
511/// arguments in order.
512#[unsafe(no_mangle)]
513unsafe extern "C" fn rswift_view_modifier_body(
514    out: *mut u8,
515    content: *mut c_void,
516    this: *const u8,
517) {
518    use std::sync::atomic::Ordering::Acquire;
519    let index = unsafe { *(this as *const i64) } as usize;
520    let modifier = {
521        let r = modifier_registry().lock().expect("modifier registry");
522        r.get(index).copied().expect("modifier index out of range")
523    };
524    let meta = CONTENT_META.load(Acquire) as *const c_void;
525    let wt = CONTENT_WT.load(Acquire) as *const c_void;
526    // The content is borrowed for the call, so it is copied into a handle SwiftUI
527    // does not own — the closure may keep it inside whatever it builds.
528    let handle = unsafe {
529        let mut bytes = vec![0u8; abi::type_bytes(meta)];
530        let vwt = &*swift_runtime_sys::SwiftABI::get_value_witness_table(meta);
531        (vwt.initialize_with_copy)(bytes.as_mut_ptr() as *mut c_void, content, meta);
532        wrap_owned(bytes, meta, wt)
533    };
534    let body = modifier.body(handle);
535    unsafe { *(out as *mut u64) = body.into_anyview_word() };
536}
537
538/// The carrier type a modifier value *is*: one `Int`, its index.
539fn modifier_carrier() -> (*const c_void, *const c_void) {
540    static C: OnceLock<(usize, usize)> = OnceLock::new();
541    let (d, m) = *C.get_or_init(|| unsafe {
542        let (d, m) =
543            crate::state::emit_int_carrier(b"RustModifier\0", b"6rswift12RustModifierV\0");
544        (d as usize, m as usize)
545    });
546    (d as *const c_void, m as *const c_void)
547}
548
549fn modifier_conformance() -> &'static Conformance {
550    static C: OnceLock<Conformance> = OnceLock::new();
551    C.get_or_init(|| unsafe {
552        let protocol = Protocol::from_symbol(c"$s7SwiftUI12ViewModifierMp");
553        assert_eq!(
554            protocol.requirement_count(),
555            6,
556            "ViewModifier changed shape — it used to be Body, its conformance, \
557             three defaulted `_make…` entries and `body(content:)`",
558        );
559        let conformance = synthesize(
560            &protocol,
561            modifier_carrier().0,
562            &[
563                Witness::Metadata(resolve::anyview_wt()),
564                Witness::Metadata(resolve::anyview_meta()),
565                Witness::Default,
566                Witness::Default,
567                Witness::Default,
568                Witness::Function(view_modifier_thunk as *const c_void),
569            ],
570        );
571
572        // Now that the conformance exists, `_ViewModifier_Content<RustModifier>`
573        // can be built — it is constrained to `ViewModifier`, so it needs both.
574        use std::sync::atomic::Ordering::Release;
575        let meta = abi::generic_metadata2(
576            resolve::vm_content_ma(),
577            modifier_carrier().1,
578            conformance.witness_table(),
579        );
580        assert!(!meta.is_null(), "_ViewModifier_Content<RustModifier> metadata");
581        let wt = abi::get_view_wt(meta);
582        assert!(!wt.is_null(), "{} !: View", crate::diag::type_name(meta));
583        CONTENT_META.store(meta as usize, Release);
584        CONTENT_WT.store(wt as usize, Release);
585
586        conformance
587    })
588}
589
590/// `.modifier(_:)` with a modifier written in Rust.
591///
592/// The result is `ModifiedContent<Self, RustModifier>` — a concrete type, not an
593/// opaque one, because `modifier<T>(_:)` says so. `T` is unconstrained there, so
594/// no witness table is passed at the call: SwiftUI finds the conformance by
595/// lookup when it renders, which is exactly why registering it matters.
596pub fn custom_modifier(view: &ViewHandle, modifier: &CustomModifier) -> ViewHandle {
597    unsafe {
598        let conformance = modifier_conformance();
599        let carrier = modifier_carrier().1;
600        let mut value = vec![0u8; abi::type_bytes(carrier)];
601        value[..8].copy_from_slice(&modifier.index.to_le_bytes());
602
603        let meta = abi::generic_metadata2(resolve::modified_content_ma(), view.meta, carrier);
604        assert!(!meta.is_null(), "ModifiedContent metadata");
605        let wt = abi::get_view_wt(meta);
606        assert!(!wt.is_null(), "{} !: View", crate::diag::type_name(meta));
607
608        let mut result = vec![0u8; abi::type_bytes(meta)];
609        let this = view.value_ptr();
610        // x0 = @in the modifier, x1 = Self.Type, x2 = T.Type,
611        // x3 = Self: View WT, x8 = @out, x20 = self.
612        core::arch::asm!("blr {f}", f = in(reg) resolve::apply_view_modifier_fn(),
613            in("x0") value.as_mut_ptr(),
614            in("x1") view.meta,
615            in("x2") carrier,
616            in("x3") view.wt,
617            in("x8") result.as_mut_ptr(),
618            in("x20") this,
619            lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
620            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
621            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
622            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
623        );
624        let _ = conformance;
625        wrap_owned(result, meta, wt)
626    }
627}
628
629// ── A `Transition` written in Rust ──
630//
631// The fourth of these, and the one with an extra argument: `body(content:phase:)`
632// is told *where in the transition* it is, and returns a different view for each
633// phase. That is what a transition is — the same closure answering "appearing",
634// "settled" and "leaving" differently.
635//
636// `.transition(_:)` for a custom `Transition` is `@_alwaysEmitIntoClient` and
637// exports nothing. Its body wraps the value in an `AnyTransition` and calls the
638// `AnyTransition` overload, so that is the route here.
639
640/// Where in a transition `body` is being asked to draw.
641#[derive(Clone, Copy, Debug, PartialEq, Eq)]
642pub enum TransitionPhase {
643    /// About to appear.
644    WillAppear,
645    /// Fully present — the phase in which a transition must change nothing.
646    Identity,
647    /// Gone.
648    DidDisappear,
649}
650
651impl TransitionPhase {
652    /// Swift's own name for the case, for looking its tag up.
653    fn case(self) -> &'static str {
654        match self {
655            Self::WillAppear => "willAppear",
656            Self::Identity => "identity",
657            Self::DidDisappear => "didDisappear",
658        }
659    }
660
661    /// The phase a tag stands for, read from reflection rather than written down.
662    ///
663    /// `TransitionPhase` is resilient, so the numbering is not something a caller
664    /// may assume — and getting it wrong would show as a transition that animates
665    /// the wrong way round, which is exactly the kind of thing that looks like a
666    /// design choice.
667    fn from_tag(tag: u8) -> Self {
668        unsafe {
669            let meta = abi::metadata0(resolve::transition_phase_ma());
670            assert!(!meta.is_null(), "TransitionPhase metadata");
671            for p in [Self::WillAppear, Self::Identity, Self::DidDisappear] {
672                let want = abi::enum_case_index(meta, p.case())
673                    .unwrap_or_else(|| panic!("TransitionPhase has no `{}` case", p.case()));
674                if want as u8 == tag {
675                    return p;
676                }
677            }
678            panic!("TransitionPhase tag {tag} is none of the three cases");
679        }
680    }
681}
682
683/// How a transition draws in each phase.
684pub trait Transitioning: Send + Sync + 'static {
685    /// Draw the content for one phase.
686    fn body(&self, content: ViewHandle, phase: TransitionPhase) -> ViewHandle;
687}
688
689impl<F> Transitioning for F
690where
691    F: Fn(ViewHandle, TransitionPhase) -> ViewHandle + Send + Sync + 'static,
692{
693    fn body(&self, content: ViewHandle, phase: TransitionPhase) -> ViewHandle {
694        self(content, phase)
695    }
696}
697
698/// A registered transition, as the one word SwiftUI stores.
699pub struct CustomTransition {
700    index: i64,
701}
702
703fn transition_registry() -> &'static Mutex<Vec<&'static dyn Transitioning>> {
704    static R: OnceLock<Mutex<Vec<&'static dyn Transitioning>>> = OnceLock::new();
705    R.get_or_init(|| Mutex::new(Vec::new()))
706}
707
708/// Register a transition. A closure taking the content and the phase is one.
709pub fn transition_fn<T: Transitioning>(transition: T) -> CustomTransition {
710    let leaked: &'static dyn Transitioning = Box::leak(Box::new(transition));
711    let mut r = transition_registry().lock().expect("transition registry");
712    r.push(leaked);
713    CustomTransition { index: r.len() as i64 - 1 }
714}
715
716// `body(content:phase:)` has one argument more than the others: the content in
717// x0, the phase as a byte in x1, `self` in x20, the result through x8.
718core::arch::global_asm!(
719    ".p2align 2",
720    ".globl _rswift_transition_thunk",
721    "_rswift_transition_thunk:",
722    "mov x3, x20",
723    "mov x2, x1",
724    "mov x1, x0",
725    "mov x0, x8",
726    "b _rswift_transition_body",
727);
728
729unsafe extern "C" {
730    #[link_name = "rswift_transition_thunk"]
731    fn transition_thunk();
732}
733
734static PLACEHOLDER_META: std::sync::atomic::AtomicUsize =
735    std::sync::atomic::AtomicUsize::new(0);
736static PLACEHOLDER_WT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
737
738/// `body(content:phase:) -> Body`, with `Body == AnyView`.
739///
740/// # Safety
741/// Reached through `rswift_transition_thunk`, which supplies the four arguments
742/// in order.
743#[unsafe(no_mangle)]
744unsafe extern "C" fn rswift_transition_body(
745    out: *mut u8,
746    content: *mut c_void,
747    phase: u64,
748    this: *const u8,
749) {
750    use std::sync::atomic::Ordering::Acquire;
751    let index = unsafe { *(this as *const i64) } as usize;
752    let transition = {
753        let r = transition_registry().lock().expect("transition registry");
754        r.get(index).copied().expect("transition index out of range")
755    };
756    let meta = PLACEHOLDER_META.load(Acquire) as *const c_void;
757    let wt = PLACEHOLDER_WT.load(Acquire) as *const c_void;
758    let handle = unsafe {
759        let mut bytes = vec![0u8; abi::type_bytes(meta)];
760        let vwt = &*swift_runtime_sys::SwiftABI::get_value_witness_table(meta);
761        (vwt.initialize_with_copy)(bytes.as_mut_ptr() as *mut c_void, content, meta);
762        wrap_owned(bytes, meta, wt)
763    };
764    let body = transition.body(handle, TransitionPhase::from_tag(phase as u8));
765    unsafe { *(out as *mut u64) = body.into_anyview_word() };
766}
767
768/// The carrier type a transition value *is*: one `Int`, its index.
769fn transition_carrier() -> (*const c_void, *const c_void) {
770    static C: OnceLock<(usize, usize)> = OnceLock::new();
771    let (d, m) = *C.get_or_init(|| unsafe {
772        let (d, m) =
773            crate::state::emit_int_carrier(b"RustTransition\0", b"6rswift14RustTransitionV\0");
774        (d as usize, m as usize)
775    });
776    (d as *const c_void, m as *const c_void)
777}
778
779fn transition_conformance() -> &'static Conformance {
780    static C: OnceLock<Conformance> = OnceLock::new();
781    C.get_or_init(|| unsafe {
782        let protocol = Protocol::from_symbol(c"$s7SwiftUI10TransitionMp");
783        assert_eq!(
784            protocol.requirement_count(),
785            5,
786            "Transition changed shape — it used to be Body, its conformance, \
787             `body(content:phase:)` and two defaulted requirements",
788        );
789        let conformance = synthesize(
790            &protocol,
791            transition_carrier().0,
792            &[
793                Witness::Metadata(resolve::anyview_wt()),
794                Witness::Metadata(resolve::anyview_meta()),
795                Witness::Function(transition_thunk as *const c_void),
796                Witness::Default,
797                Witness::Default,
798            ],
799        );
800
801        use std::sync::atomic::Ordering::Release;
802        let meta = abi::generic_metadata2(
803            resolve::placeholder_content_ma(),
804            transition_carrier().1,
805            conformance.witness_table(),
806        );
807        assert!(!meta.is_null(), "PlaceholderContentView<RustTransition> metadata");
808        let wt = abi::get_view_wt(meta);
809        assert!(!wt.is_null(), "{} !: View", crate::diag::type_name(meta));
810        PLACEHOLDER_META.store(meta as usize, Release);
811        PLACEHOLDER_WT.store(wt as usize, Release);
812
813        conformance
814    })
815}
816
817/// `.transition(_:)` with a transition written in Rust.
818///
819/// Wrapped in an `AnyTransition` on the way, because that is what
820/// `transition<T: Transition>(_:)` does: it is `@_alwaysEmitIntoClient` and
821/// exports no symbol, so the binding calls what its body calls.
822pub fn custom_transition(view: &ViewHandle, transition: &CustomTransition) -> ViewHandle {
823    unsafe {
824        let conformance = transition_conformance();
825        let carrier = transition_carrier().1;
826        let mut value = vec![0u8; abi::type_bytes(carrier)];
827        value[..8].copy_from_slice(&transition.index.to_le_bytes());
828
829        // `AnyTransition.init<T: Transition>(_:)` — x0 = @in T, x1 = T.Type,
830        // x2 = T: Transition WT, and the box comes back in x0. `AnyTransition` is
831        // `@frozen` around one class reference, so it is one word.
832        let boxed: u64;
833        core::arch::asm!("blr {f}", f = in(reg) resolve::any_transition_init(),
834            in("x0") value.as_mut_ptr(),
835            in("x1") carrier,
836            in("x2") conformance.witness_table(),
837            lateout("x0") boxed,
838            lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
839            lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x8") _,
840            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
841            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
842            lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
843            clobber_abi("C"),
844        );
845        assert!(boxed != 0, "AnyTransition.init returned nothing");
846
847        super::opaque_modifier_for_transition(view, boxed)
848    }
849}
850
851// ── A `CustomAnimation` written in Rust ──
852//
853// The timing curve itself, not one of SwiftUI's. This is the most interesting of
854// the five, because `animate` is generic over *any* `VectorArithmetic` — a
855// `Double`, a `CGSize`, an `AnimatablePair` of either — and the implementation
856// is not allowed to know which. So it does not try: the Rust closure answers
857// only "how far along is this, at time t", and the witness applies that fraction
858// to whatever it was handed by calling the protocol's own `scale(by:)` through
859// that type's witness table.
860//
861// `nil` means the animation has finished. That is how SwiftUI knows when to stop
862// asking, and returning a value forever would leave it running.
863
864/// A timing curve written in Rust.
865pub trait Animating: Send + Sync + 'static {
866    /// How much of the value to apply at `time` seconds in, or `None` when the
867    /// animation is over.
868    ///
869    /// `0.0` is the starting value and `1.0` the target, but nothing stops a
870    /// curve from overshooting — that is what a spring does.
871    fn progress(&self, time: f64) -> Option<f64>;
872}
873
874impl<F> Animating for F
875where
876    F: Fn(f64) -> Option<f64> + Send + Sync + 'static,
877{
878    fn progress(&self, time: f64) -> Option<f64> {
879        self(time)
880    }
881}
882
883/// A registered animation, as the one word SwiftUI stores.
884pub struct CustomAnimationCurve {
885    index: i64,
886}
887
888fn animation_registry() -> &'static Mutex<Vec<&'static dyn Animating>> {
889    static R: OnceLock<Mutex<Vec<&'static dyn Animating>>> = OnceLock::new();
890    R.get_or_init(|| Mutex::new(Vec::new()))
891}
892
893/// Register a timing curve. A closure from time to progress is one.
894pub fn animation_fn<A: Animating>(curve: A) -> CustomAnimationCurve {
895    let leaked: &'static dyn Animating = Box::leak(Box::new(curve));
896    let mut r = animation_registry().lock().expect("animation registry");
897    r.push(leaked);
898    CustomAnimationCurve { index: r.len() as i64 - 1 }
899}
900
901// `animate(value:time:context:)` arrives as
902//   x8 = @out V?, x0 = @in V, d0 = time, x1 = inout AnimationContext<V>,
903//   x2 = V.Type, x3 = V: VectorArithmetic WT, x20 = self
904// and every integer argument has to move up one place. They are written in
905// descending order of destination so none is overwritten before it is read.
906core::arch::global_asm!(
907    ".p2align 2",
908    ".globl _rswift_custom_animation_thunk",
909    "_rswift_custom_animation_thunk:",
910    "mov x5, x20",
911    "mov x4, x3",
912    "mov x3, x2",
913    "mov x2, x1",
914    "mov x1, x0",
915    "mov x0, x8",
916    "b _rswift_custom_animation_animate",
917);
918
919unsafe extern "C" {
920    #[link_name = "rswift_custom_animation_thunk"]
921    fn custom_animation_thunk();
922}
923
924/// `animate(value:time:context:) -> V?`.
925///
926/// # Safety
927/// Reached through `rswift_custom_animation_thunk`, which supplies the six
928/// integer arguments in order and leaves the time in d0.
929#[unsafe(no_mangle)]
930#[allow(clippy::too_many_arguments)]
931unsafe extern "C" fn rswift_custom_animation_animate(
932    out: *mut u8,
933    value: *const c_void,
934    _context: *mut c_void,
935    v_meta: *const c_void,
936    v_wt: *const c_void,
937    this: *const u8,
938    time: f64,
939) {
940    let index = unsafe { *(this as *const i64) } as usize;
941    let curve = {
942        let r = animation_registry().lock().expect("animation registry");
943        r.get(index).copied().expect("animation index out of range")
944    };
945    unsafe {
946        match curve.progress(time) {
947            // Finished. `V?` with an address-only payload spends a spare bit
948            // pattern or a tag byte on `.none`; which of those is the payload's
949            // business, so the witness is asked rather than a byte written.
950            None => abi::store_optional_tag_at(out, v_meta, false),
951            Some(fraction) => {
952                let vwt = &*swift_runtime_sys::SwiftABI::get_value_witness_table(v_meta);
953                (vwt.initialize_with_copy)(
954                    out as *mut c_void,
955                    value as *mut c_void,
956                    v_meta,
957                );
958                // `scale(by:)` is a *mutating* requirement, so `self` is an
959                // `inout` in x20 and the factor is in d0. Reached through its
960                // dispatch thunk, which is how a generic caller reaches any
961                // requirement it cannot name a concrete implementation for.
962                core::arch::asm!("blr {f}", f = in(reg) resolve::vector_scale_by(),
963                    in("d0") fraction,
964                    in("x0") v_meta,
965                    in("x1") v_wt,
966                    in("x20") out,
967                    lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
968                    lateout("x6") _, lateout("x7") _, lateout("x8") _,
969                    lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
970                    lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
971                    lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
972                );
973                abi::store_optional_tag_at(out, v_meta, true);
974            }
975        }
976    }
977}
978
979/// The carrier type an animation value *is*: one `Int`, its index.
980fn animation_carrier() -> (*const c_void, *const c_void) {
981    static C: OnceLock<(usize, usize)> = OnceLock::new();
982    let (d, m) = *C.get_or_init(|| unsafe {
983        let (d, m) =
984            crate::state::emit_int_carrier(b"RustAnimation\0", b"6rswift13RustAnimationV\0");
985        (d as usize, m as usize)
986    });
987    (d as *const c_void, m as *const c_void)
988}
989
990fn animation_conformance() -> &'static Conformance {
991    static C: OnceLock<Conformance> = OnceLock::new();
992    C.get_or_init(|| unsafe {
993        let protocol = Protocol::from_symbol(c"$s7SwiftUI15CustomAnimationMp");
994        assert_eq!(
995            protocol.requirement_count(),
996            4,
997            "CustomAnimation changed shape — it used to be Hashable, `animate` and \
998             two defaulted requirements",
999        );
1000        synthesize(
1001            &protocol,
1002            animation_carrier().0,
1003            &[
1004                // `CustomAnimation: Hashable`, and the carrier is one `Int` — the
1005                // same size, alignment and bit pattern. So `Int`'s own `Hashable`
1006                // conformance describes it exactly: `hashValue` reads one word of
1007                // `self`, `hash(into:)` feeds one word to a `Hasher`, and `==`
1008                // compares two. Borrowing it is an ABI statement, not a language
1009                // one, and it is the same trade `custom_layout` makes for
1010                // `Animatable`.
1011                Witness::Metadata(resolve::int_hashable_wt()),
1012                Witness::Function(custom_animation_thunk as *const c_void),
1013                Witness::Default,
1014                Witness::Default,
1015            ],
1016        )
1017    })
1018}
1019
1020impl CustomAnimationCurve {
1021    /// The curve as an `Animation`, for anything that takes one.
1022    ///
1023    /// `withAnimation` is the main such thing —
1024    /// [`StatefulView::set_animated_with`](crate::state::StatefulView::set_animated_with)
1025    /// takes exactly this word — and driving a state change with it is the only
1026    /// way to see the curve actually run: a static render shows one frame, and
1027    /// one frame of an animation is indistinguishable from no animation.
1028    ///
1029    /// A `CustomAnimation` is not an `Animation`; it is what one can be built
1030    /// from, which is what `Animation.init<A: CustomAnimation>(_:)` is for.
1031    pub fn animation_object(&self) -> u64 {
1032        unsafe { self.build_animation() }
1033    }
1034
1035    /// # Safety
1036    /// Calls into SwiftUI.
1037    unsafe fn build_animation(&self) -> u64 {
1038        unsafe { build_custom_animation(self.index) }
1039    }
1040}
1041
1042/// `Animation(curve)` for the curve registered at `index`.
1043///
1044/// # Safety
1045/// Calls into SwiftUI; `index` must be a registered curve.
1046unsafe fn build_custom_animation(index: i64) -> u64 {
1047    unsafe {
1048        let conformance = animation_conformance();
1049        let carrier = animation_carrier().1;
1050        let mut held = vec![0u8; abi::type_bytes(carrier)];
1051        held[..8].copy_from_slice(&index.to_le_bytes());
1052
1053        // `Animation.init<A: CustomAnimation>(_:)` — x0 = @in A, x1 = A.Type,
1054        // x2 = A: CustomAnimation WT, and the box comes back in x0.
1055        let anim: u64;
1056        core::arch::asm!("blr {f}", f = in(reg) resolve::animation_custom_init(),
1057            in("x0") held.as_mut_ptr(),
1058            in("x1") carrier,
1059            in("x2") conformance.witness_table(),
1060            lateout("x0") anim,
1061            lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
1062            lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x8") _,
1063            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
1064            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
1065            lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
1066            clobber_abi("C"),
1067        );
1068        assert!(anim != 0, "Animation.init returned nothing");
1069        anim
1070    }
1071}
1072
1073/// `.animation(Animation(curve), value:)` with a curve written in Rust.
1074pub fn custom_animation(
1075    view: &ViewHandle,
1076    curve: &CustomAnimationCurve,
1077    value: i64,
1078) -> ViewHandle {
1079    unsafe {
1080        let anim = build_custom_animation(curve.index);
1081        super::animation_word(view, anim, value)
1082    }
1083}