Skip to main content

swiftui_native/views/
primitives.rs

1//! Views.
2//!
3//! * Modifiers
4
5use super::*;
6use crate::abi;
7use crate::resolve;
8use core::ffi::c_void;
9use crate::mangle::{self, swift_type};
10
11// ── Views ──
12
13/// `LocalizedStringKey.init(stringLiteral:)`.
14///
15/// LocalizedStringKey is 32 bytes, so it comes back in x0-x3 — all four. x3 is
16/// the `arguments` array (normally `_swiftEmptyArrayStorage`). Dropping it
17/// leaves a null array inside the Text's storage, which survives construction
18/// and only faults much later, when SwiftUI diffs the view and compares the two
19/// argument arrays.
20pub(crate) unsafe fn localized_string_key(s0: u64, s1: u64) -> (u64, u64, u64, u64) {
21    let (k0, k1, k2, k3): (u64, u64, u64, u64);
22    unsafe {
23        core::arch::asm!("blr {f}", f = in(reg) resolve::lsk_init(),
24            inout("x0") s0 => k0, inout("x1") s1 => k1,
25            lateout("x2") k2, lateout("x3") k3,
26            in("x20") resolve::lsk_meta(),
27            lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
28            lateout("x8") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
29            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
30            lateout("x16") _, lateout("x17") _, lateout("lr") _, clobber_abi("C"),
31        );
32    }
33    (k0, k1, k2, k3)
34}
35
36/// Create a `Text` view.
37pub fn text(s: &str) -> ViewHandle {
38    unsafe {
39        let ss = abi::swift_string(s);
40        let s0 = u64::from_le_bytes(ss[..8].try_into().unwrap());
41        let s1 = u64::from_le_bytes(ss[8..].try_into().unwrap());
42
43        let (k0, k1, k2, k3) = localized_string_key(s0, s1);
44
45        // Text.init(_:tableName:bundle:comment:)
46        //   x0-x3 = LocalizedStringKey
47        //   x4,x5 = tableName: String?   (nil)
48        //   x6    = bundle: Bundle?      (nil)
49        //   x7 +2 stack words = comment: StaticString?  (nil)
50        //
51        // `comment` spills past x7, so zero 32 bytes of stack before the call —
52        // otherwise the callee reads whatever Rust happened to leave there.
53        let (t0, t1, t2, t3): (u64, u64, u64, u64);
54        core::arch::asm!(
55            "sub sp, sp, #32",
56            "stp xzr, xzr, [sp]",
57            "stp xzr, xzr, [sp, #16]",
58            "blr {f}",
59            "add sp, sp, #32",
60            f = in(reg) resolve::text_lsk_init(),
61            inout("x0") k0 => t0, inout("x1") k1 => t1,
62            inout("x2") k2 => t2, inout("x3") k3 => t3,
63            in("x4") 0u64, in("x5") 0u64, in("x6") 0u64, in("x7") 0u64,
64            in("x20") resolve::text_meta(),
65            lateout("x8") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
66            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
67            lateout("x16") _, lateout("x17") _, lateout("lr") _, clobber_abi("C"),
68        );
69
70        let mut buf = [0u8; 32];
71        buf[..8].copy_from_slice(&t0.to_le_bytes());
72        buf[8..16].copy_from_slice(&t1.to_le_bytes());
73        buf[16..24].copy_from_slice(&t2.to_le_bytes());
74        buf[24..32].copy_from_slice(&t3.to_le_bytes());
75        wrap_buf(&buf, resolve::text_meta(), resolve::text_wt())
76    }
77}
78
79/// Create a `Text` with font size, weight and colour applied.
80pub fn styled_text(s: &str, size: f64, weight: i32, r: f64, g: f64, b: f64, a: f64) -> ViewHandle {
81    let t = font(&text(s), size, weight > 0);
82    foreground_style_color(&t, r, g, b, a)
83}
84
85/// Create a bold `Text` at `size` points (`size <= 0` keeps the default size).
86pub fn bold_text(s: &str, size: f64) -> ViewHandle {
87    let t = text(s);
88    if size > 0.0 { font(&t, size, true) } else { bold(&t) }
89}
90
91/// Build a raw `SwiftUI.Color` value (8 bytes), not wrapped in `AnyView`.
92///
93/// Modifiers that take a `ShapeStyle` want the `Color` itself. Handing them the
94/// `AnyView` wrapper instead type-checks at the ABI level — both are a single
95/// class reference — and then misbehaves inside SwiftUI.
96pub(crate) unsafe fn color_value(r: f64, g: f64, b: f64, a: f64) -> u64 {
97    let mut cs: u8 = 0; // RGBColorSpace.sRGB
98    let result: u64;
99    unsafe {
100        core::arch::asm!("blr {f}", f = in(reg) resolve::color_init(),
101            in("x0") &mut cs as *mut u8, in("d0") r, in("d1") g, in("d2") b, in("d3") a,
102            in("x20") resolve::color_meta(),
103            lateout("x0") result,
104            lateout("x1") _, lateout("x2") _, lateout("x3") _,
105            lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
106            lateout("x8") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
107            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
108            lateout("x16") _, lateout("x17") _, lateout("lr") _, clobber_abi("C"),
109        );
110    }
111    result
112}
113
114/// The `Color` value behind a set of components.
115///
116/// What [`Shape::fill`] and the other style-taking calls want: a `Color` is one
117/// word once built, and building it is a call into SwiftUI rather than a
118/// packing of the four numbers.
119pub fn color_value_of(c: crate::Color) -> u64 {
120    unsafe { color_value(c.r, c.g, c.b, c.a) }
121}
122
123/// `Color.opacity(_:)` — a `Color` with its alpha scaled.
124///
125/// Not the `.opacity(_:)` view modifier. That one composites the layer a view
126/// drew; this one changes the colour before anything is drawn, and the two
127/// differ wherever the result is clipped or blended — the antialiased edge of a
128/// `clipShape` picks up a visibly different value.
129pub fn color_opacity(color: u64, opacity: f64) -> u64 {
130    unsafe {
131        let out: u64;
132        core::arch::asm!("blr {f}", f = in(reg) resolve::color_opacity(),
133            in("d0") opacity,
134            in("x0") color,
135            lateout("x0") out,
136            lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
137            lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x8") _,
138            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
139            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
140            lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
141            clobber_abi("C"),
142        );
143        out
144    }
145}
146
147/// `Color.mix(with:by:)` — blend two colours, perceptually.
148///
149/// `(@guaranteed Color, Double, @in_guaranteed Gradient.ColorSpace,
150/// @guaranteed Color) -> @owned Color`: the *other* colour in x0, the fraction
151/// in d0, the colour space by address in x1, and the receiver in x2. Swift's
152/// default for the space is `.perceptual`, from a default-argument thunk.
153pub fn color_mix(color: u64, with: u64, by: f64) -> u64 {
154    color_mix_in(color, with, by, super::GradientColorSpace::Perceptual)
155}
156
157/// `Color.mix(with:by:in:)` — the same blend in whichever space is named.
158pub fn color_mix_in(
159    color: u64,
160    with: u64,
161    by: f64,
162    in_space: super::GradientColorSpace,
163) -> u64 {
164    unsafe {
165        let space_meta = abi::metadata0(resolve::gradient_color_space_ma());
166        assert!(!space_meta.is_null(), "Gradient.ColorSpace metadata");
167        let mut space = super::static_getter_indirect(
168            in_space.resolved(),
169            space_meta,
170            abi::type_bytes(space_meta),
171        );
172        let out: u64;
173        core::arch::asm!("blr {f}", f = in(reg) resolve::color_mix(),
174            in("x0") with,
175            in("d0") by,
176            in("x1") space.as_mut_ptr(),
177            in("x2") color,
178            lateout("x0") out,
179            lateout("x3") _, lateout("x4") _, lateout("x5") _, lateout("x6") _,
180            lateout("x7") _, lateout("x8") _, lateout("x9") _, lateout("x10") _,
181            lateout("x11") _, lateout("x12") _, lateout("x13") _, lateout("x14") _,
182            lateout("x15") _, lateout("x16") _, lateout("x17") _, lateout("lr") _,
183            lateout("x20") _, lateout("x21") _, clobber_abi("C"),
184        );
185        out
186    }
187}
188
189/// `Color.exposureAdjust(_:)` — brighten or darken by stops.
190///
191/// There is no `exposureAdjust` symbol: it is `@inlinable`, and its whole body
192/// is `_linearScale(Float(exp2(ev)))` — a truncation to `Float`, a `exp2`, and
193/// one call to a function that *is* exported. So this does what the inlined
194/// body does, which is what `selectionDisabled` and `task` needed too.
195///
196/// The `Float` goes in s0, the 32-bit view of d0, and the colour in x0.
197pub fn color_exposure_adjust(color: u64, stops: f64) -> u64 {
198    unsafe {
199        let scale = f64::exp2(stops) as f32;
200        let out: u64;
201        core::arch::asm!("blr {f}", f = in(reg) resolve::color_linear_scale(),
202            in("s0") scale,
203            in("x0") color,
204            lateout("x0") out,
205            lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
206            lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x8") _,
207            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
208            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
209            lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
210            clobber_abi("C"),
211        );
212        out
213    }
214}
215
216/// `Color.headroom(_:)` — the colour's allowed brightness past white.
217///
218/// The `Double?` is payload then tag in x0/x1, and the colour follows in x2
219/// rather than arriving in x20: like `Color.opacity`, this is an extension
220/// method whose receiver is an ordinary argument.
221pub fn color_headroom(color: u64, headroom: Option<f64>) -> u64 {
222    unsafe {
223        let (bits, tag) = match headroom {
224            Some(v) => (v.to_bits(), 0u64),
225            None => (0, 1),
226        };
227        let out: u64;
228        core::arch::asm!("blr {f}", f = in(reg) resolve::color_headroom(),
229            in("x0") bits, in("x1") tag, in("x2") color,
230            lateout("x0") out,
231            lateout("x3") _, lateout("x4") _, lateout("x5") _, lateout("x6") _,
232            lateout("x7") _, lateout("x8") _, lateout("x9") _, lateout("x10") _,
233            lateout("x11") _, lateout("x12") _, lateout("x13") _, lateout("x14") _,
234            lateout("x15") _, lateout("x16") _, lateout("x17") _, lateout("lr") _,
235            lateout("x20") _, lateout("x21") _, clobber_abi("C"),
236        );
237        out
238    }
239}
240
241/// `Color.gradient` — the colour as a gentle vertical gradient.
242///
243/// An `AnyGradient`, not a `Color`: it is a `ShapeStyle` of its own, so it goes
244/// to [`Shape::fill_style`] with its own metadata and witness table rather than
245/// through the `Color` overloads. Eight bytes, so it travels in a register both
246/// ways, and the receiving colour goes in x0 rather than x20.
247pub fn color_gradient(color: u64) -> (Vec<u8>, *const c_void, *const c_void) {
248    unsafe {
249        let out: u64;
250        core::arch::asm!("blr {f}", f = in(reg) resolve::color_gradient(),
251            in("x0") color,
252            lateout("x0") out,
253            lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
254            lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x8") _,
255            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
256            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
257            lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
258            clobber_abi("C"),
259        );
260        (out.to_le_bytes().to_vec(), resolve::any_gradient_meta(), resolve::any_gradient_wt())
261    }
262}
263
264/// Create a view of a `Color` value that is already built.
265pub fn color_view(color: u64) -> ViewHandle {
266    unsafe {
267        let buf = color.to_le_bytes();
268        wrap_buf(&buf, resolve::color_meta(), resolve::color_wt())
269    }
270}
271
272/// Create a `Color` view.
273pub fn color(r: f64, g: f64, b: f64, a: f64) -> ViewHandle {
274    unsafe {
275        let buf = color_value(r, g, b, a).to_le_bytes();
276        wrap_buf(&buf, resolve::color_meta(), resolve::color_wt())
277    }
278}
279
280/// Create a `Spacer`.
281pub fn spacer() -> ViewHandle {
282    unsafe {
283        let r0: u64; let r1: u64;
284        core::arch::asm!("blr {f}", f = in(reg) resolve::spacer_init(),
285            // `Optional<CGFloat>` is a Double plus a tag byte, both loadable:
286            // payload in x0, tag in x1. Tag 0 is `.some`, so `.none` needs 1 —
287            // with 0 the spacer is built with an explicit `minLength` of zero,
288            // which lays out identically and is a different value.
289            in("x0") 0u64, in("x1") 1u64, // Optional<CGFloat>.none
290            in("x20") resolve::spacer_meta(),
291            lateout("x0") r0, lateout("x1") r1,
292            lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
293            lateout("x8") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
294            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
295            lateout("x16") _, lateout("x17") _, lateout("lr") _, clobber_abi("C"),
296        );
297        let mut buf = [0u8; 16];
298        buf[..8].copy_from_slice(&r0.to_le_bytes());
299        buf[8..16].copy_from_slice(&r1.to_le_bytes());
300        wrap_buf(&buf, resolve::spacer_meta(), resolve::spacer_wt())
301    }
302}
303
304/// `Edge.Set.all` — one bit per edge.
305///
306/// The raw value is an `Int8`, and only the low four bits are edges. Setting
307/// the rest (`0xFF`) still reads as "every edge" everywhere it is used, so it
308/// lays out identically; it just is not the value Swift writes, and prints as
309/// `rawValue: -1` instead of `15`.
310pub(crate) const EDGE_SET_ALL: u64 = 0b1111;
311
312/// Create an `EmptyView`.
313pub fn empty_view() -> ViewHandle {
314    unsafe { ViewHandle::wrap(core::ptr::null(), resolve::empty_meta(), resolve::empty_wt()) }
315}
316
317/// Create a `Divider`.
318pub fn divider() -> ViewHandle {
319    unsafe { ViewHandle::wrap(core::ptr::null(), resolve::divider_meta(), resolve::divider_wt()) }
320}
321
322/// Create an `Image` from an SF Symbol name.
323pub fn system_image(name: &str) -> ViewHandle {
324    unsafe {
325        let ss = abi::swift_string(name);
326        let s0 = u64::from_le_bytes(ss[..8].try_into().unwrap());
327        let s1 = u64::from_le_bytes(ss[8..].try_into().unwrap());
328        let result: u64;
329        core::arch::asm!("blr {f}", f = in(reg) resolve::image_sysname_init(),
330            in("x0") s0, in("x1") s1, in("x20") resolve::image_meta(),
331            lateout("x0") result,
332            lateout("x1") _, lateout("x2") _, lateout("x3") _,
333            lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
334            lateout("x8") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
335            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
336            lateout("x16") _, lateout("x17") _, lateout("lr") _, clobber_abi("C"),
337        );
338        let buf = result.to_le_bytes();
339        wrap_buf(&buf, resolve::image_meta(), resolve::image_wt())
340    }
341}
342
343mangle::result_types! {
344    /// `SwiftUI.ProgressView<SwiftUI.EmptyView, SwiftUI.EmptyView>`
345    /// `ProgressView<EmptyView, EmptyView>`
346    pub(crate) PROGRESS_T = ty("ProgressView").of(vec![ty("EmptyView"), ty("EmptyView")]);
347    /// `SwiftUI.Toggle<SwiftUI.Text>`
348    /// `Toggle<Text>`
349    pub(crate) TOGGLE_T = ty("Toggle").of(vec![ty("Text")]);
350    /// `Toggle<AnyView>` — a toggle whose label is any view.
351    pub(crate) TOGGLE_ANY_T = ty("Toggle").of(vec![ty("AnyView")]);
352    /// `SwiftUI.TextField<SwiftUI.Text>`
353    /// `TextField<Text>`
354    pub(crate) TEXTFIELD_T = ty("TextField").of(vec![ty("Text")]);
355    /// `SwiftUI.SecureField<SwiftUI.Text>`
356    /// `SecureField<Text>`
357    pub(crate) SECUREFIELD_T = ty("SecureField").of(vec![ty("Text")]);
358    /// `SwiftUI.Slider<SwiftUI.EmptyView, SwiftUI.EmptyView>`
359    /// `Slider<EmptyView, EmptyView>`
360    pub(crate) SLIDER_T = ty("Slider").of(vec![ty("EmptyView"), ty("EmptyView")]);
361}
362
363/// Create an indeterminate `ProgressView` — the spinner, with no value.
364///
365/// `ProgressView()` is not `progress(0, 1)`: it is a different initialiser
366/// whose `Label` and `CurrentValueLabel` are both `EmptyView`, and it is the
367/// one every `ProgressView()` in the documentation means.
368pub fn progress_indeterminate() -> ViewHandle {
369    unsafe {
370        let ty = mangle::ty("ProgressView")
371            .of(vec![mangle::ty("EmptyView"), mangle::ty("EmptyView")])
372            .mangle();
373        let meta = abi::resolve_type_by_mangled_name(ty.as_bytes());
374        assert!(!meta.is_null(), "ProgressView<EmptyView, EmptyView> metadata");
375        let mut value = vec![0u8; abi::type_bytes(meta)];
376        core::arch::asm!("blr {f}", f = in(reg) resolve::progress_indeterminate_init(),
377            in("x8") value.as_mut_ptr(),
378            in("x20") meta,
379            lateout("x0") _, lateout("x1") _, lateout("x2") _, lateout("x3") _,
380            lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
381            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
382            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
383            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
384        );
385        let wt = abi::get_view_wt(meta);
386        assert!(!wt.is_null(), "ProgressView !: View");
387        wrap_buf(&value, meta, wt)
388    }
389}
390
391/// Create a determinate `ProgressView`.
392pub fn progress(value: f64, total: f64) -> ViewHandle {
393    unsafe {
394        // The initialiser is generic over `V: BinaryFloatingPoint`, so both
395        // arguments are passed *indirectly* even though Double is loadable.
396        let double_meta = resolve::double_meta();
397        let bfp_wt =
398            abi::get_view_wt_for_proto(double_meta, resolve::binary_floating_point_proto());
399        assert!(!bfp_wt.is_null(), "Double: BinaryFloatingPoint");
400
401        let opt_meta = abi::resolve_type_by_mangled_name(mangle::optional(mangle::DOUBLE).as_bytes());
402        assert!(!opt_meta.is_null(), "Optional<Double> metadata");
403        let mut value_buf = vec![0u8; abi::type_bytes(opt_meta)];
404        value_buf[..8].copy_from_slice(&value.to_le_bytes());
405        abi::store_optional_tag(&mut value_buf, double_meta, true);
406
407        let mut total_buf = total.to_le_bytes();
408
409        let (meta, wt, size) = result_type(&PROGRESS_T);
410        let mut result = vec![0u8; size];
411        // ProgressView.init(value:total:)
412        //   x0 = @in Optional<V>, x1 = @in V,
413        //   x2 = V.Type, x3 = V: BinaryFloatingPoint WT, x8 = @out
414        core::arch::asm!("blr {f}", f = in(reg) resolve::progress_init(),
415            in("x0") value_buf.as_mut_ptr(),
416            in("x1") total_buf.as_mut_ptr(),
417            in("x2") double_meta,
418            in("x3") bfp_wt,
419            in("x8") result.as_mut_ptr(),
420            lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
421            lateout("x9") _, lateout("x10") _, lateout("x11") _,
422            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
423            lateout("x16") _, lateout("x17") _, lateout("lr") _,
424            lateout("x20") _, lateout("x21") _,
425            clobber_abi("C"),
426        );
427        wrap_owned(result, meta, wt)
428    }
429}
430
431
432
433/// Build `Binding.constant(_:)` for a value of the given type.
434///
435/// A constant binding is enough to render: the displayed state is whatever the
436/// getter returns right now, and Rust drives changes by rebuilding the tree and
437/// calling [`crate::HostingView::set_root`].
438pub(crate) unsafe fn constant_binding(
439    value: &[u8],
440    value_meta: *const c_void,
441    binding_mangled: &[u8],
442) -> Vec<u8> {
443    unsafe {
444        let binding_meta = abi::resolve_type_by_mangled_name(binding_mangled);
445        assert!(!binding_meta.is_null(), "Binding metadata");
446        let mut val = value.to_vec();
447        let mut out = vec![0u8; abi::type_bytes(binding_meta)];
448        // static Binding.constant(_: Value) -> Binding<Value>
449        //   x0 = @in Value, x1 = Value.Type, x8 = @out Binding
450        //
451        // `Value` arrives as an ordinary generic argument in x1 — not through
452        // the self register, even though this is a static method.
453        core::arch::asm!("blr {f}", f = in(reg) resolve::binding_constant(),
454            in("x0") val.as_mut_ptr(),
455            in("x1") value_meta,
456            in("x8") out.as_mut_ptr(),
457            lateout("x2") _, lateout("x3") _, lateout("x4") _,
458            lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x9") _,
459            lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
460            lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
461            lateout("lr") _, lateout("x20") _, lateout("x21") _, clobber_abi("C"),
462        );
463        let _ = binding_meta;
464        out
465    }
466}
467
468/// Create a `Toggle` with a text label.
469pub fn toggle(label: &str, is_on: bool) -> ViewHandle {
470    unsafe {
471        let ss = abi::swift_string(label);
472        let s0 = u64::from_le_bytes(ss[..8].try_into().unwrap());
473        let s1 = u64::from_le_bytes(ss[8..].try_into().unwrap());
474        let (k0, k1, k2, k3) = localized_string_key(s0, s1);
475
476        let binding = constant_binding(&[u8::from(is_on)], resolve::sym(c"$sSbN"), mangle::bound(&swift_type!(SwiftUI.Binding), &[mangle::BOOL]).as_bytes());
477        // Binding<Bool> is 17 bytes, so it occupies three registers.
478        let b0 = u64::from_le_bytes(binding[0..8].try_into().unwrap());
479        let b1 = u64::from_le_bytes(binding[8..16].try_into().unwrap());
480        let b2 = binding[16] as u64;
481
482        let (meta, wt, size) = result_type(&TOGGLE_T);
483        let mut result = vec![0u8; size];
484        // Toggle<Text>.init(_: LocalizedStringKey, isOn: Binding<Bool>)
485        //   x0-x3 = key, x4-x6 = binding, x8 = @out
486        core::arch::asm!("blr {f}", f = in(reg) resolve::toggle_init(),
487            in("x0") k0, in("x1") k1, in("x2") k2, in("x3") k3,
488            in("x4") b0, in("x5") b1, in("x6") b2,
489            in("x8") result.as_mut_ptr(),
490            in("x20") meta,
491            lateout("x7") _,
492            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
493            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
494            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
495        );
496        wrap_owned(result, meta, wt)
497    }
498}
499
500
501
502
503
504/// A closure that does nothing, for callbacks the Rust API does not expose.
505/// Paired with a null context, which Swift retains and releases harmlessly.
506pub(crate) unsafe extern "C" fn noop_closure() {}
507
508/// Build `Binding<String>.constant(_:)` and return its four words.
509pub(crate) unsafe fn string_binding(value: &str) -> [u64; 4] {
510    unsafe {
511        let ss = abi::swift_string(value);
512        let b = constant_binding(&ss, resolve::string_meta_sym(), mangle::bound(&swift_type!(SwiftUI.Binding), &[mangle::STRING]).as_bytes());
513        binding_words(&b)
514    }
515}
516
517/// The four words of a `Binding<String>` built by [`super::text_binding`].
518///
519/// `Binding` caches its value inline, so `Binding<String>` is 32 bytes and
520/// travels in x4–x7 rather than indirectly.
521pub(crate) fn binding_words(b: &[u8]) -> [u64; 4] {
522    let mut w = [0u64; 4];
523    for (i, slot) in w.iter_mut().enumerate() {
524        *slot = u64::from_le_bytes(b[i * 8..i * 8 + 8].try_into().expect("binding word"));
525    }
526    w
527}
528
529/// Create a `TextField` showing a fixed string, which the user cannot edit.
530///
531/// The string becomes a `Binding.constant` and SwiftUI treats that as
532/// read-only: keystrokes have nowhere to be written, so the field ignores them.
533/// [`textfield_bound`] is the one that can be typed into.
534pub fn textfield(placeholder: &str, value: &str) -> ViewHandle {
535    unsafe { textfield_with_binding(placeholder, &string_binding(value)) }
536}
537
538/// Create a `TextField` wired to Rust state — typing writes into `state`.
539pub fn textfield_bound(placeholder: &str, state: &super::BoundState<String>) -> ViewHandle {
540    unsafe { textfield_with_binding(placeholder, &binding_words(&super::text_binding(state))) }
541}
542
543/// Shared tail of [`textfield`] and [`textfield_bound`].
544unsafe fn textfield_with_binding(placeholder: &str, b: &[u64; 4]) -> ViewHandle {
545    unsafe {
546        let ss = abi::swift_string(placeholder);
547        let s0 = u64::from_le_bytes(ss[..8].try_into().unwrap());
548        let s1 = u64::from_le_bytes(ss[8..].try_into().unwrap());
549        let (k0, k1, k2, k3) = localized_string_key(s0, s1);
550
551        let (meta, wt, size) = result_type(&TEXTFIELD_T);
552        let mut result = vec![0u8; size];
553        let noop = noop_closure as *const c_void;
554        // TextField.init(_:text:onEditingChanged:onCommit:)
555        //   x0-x3 = key, x4-x7 = Binding<String>,
556        //   [sp] = onEditingChanged (fn, ctx), [sp+16] = onCommit (fn, ctx)
557        core::arch::asm!(
558            "sub sp, sp, #32",
559            "stp {n}, xzr, [sp]",
560            "stp {n}, xzr, [sp, #16]",
561            "blr {f}",
562            "add sp, sp, #32",
563            f = in(reg) resolve::textfield_init(),
564            n = in(reg) noop,
565            in("x0") k0, in("x1") k1, in("x2") k2, in("x3") k3,
566            in("x4") b[0], in("x5") b[1], in("x6") b[2], in("x7") b[3],
567            in("x8") result.as_mut_ptr(),
568            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
569            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
570            lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
571            clobber_abi("C"),
572        );
573        wrap_owned(result, meta, wt)
574    }
575}
576
577/// Create a `TextField` that grows along `axis` as it is typed into.
578///
579/// `.vertical` is the multi-line field — the one that starts a line high and
580/// adds rows as text wraps, which is what a comment box or a note is. Only the
581/// deprecated `onEditingChanged:onCommit:` initialiser was bound, so every
582/// field was one line whatever went in it.
583pub fn textfield_axis(
584    placeholder: &str,
585    state: &super::BoundState<String>,
586    axis: super::Axis,
587) -> ViewHandle {
588    unsafe {
589        let b = binding_words(&super::text_binding(state));
590        let ss = abi::swift_string(placeholder);
591        let s0 = u64::from_le_bytes(ss[..8].try_into().unwrap());
592        let s1 = u64::from_le_bytes(ss[8..].try_into().unwrap());
593        let (k0, k1, k2, k3) = localized_string_key(s0, s1);
594
595        let (meta, wt, size) = result_type(&TEXTFIELD_T);
596        let mut result = vec![0u8; size];
597        // TextField.init(_:text:axis:)
598        //   x0-x3 = key, x4-x7 = Binding<String>, and `Axis` — a frozen
599        //   two-case enum, so one byte — has no register left and goes on the
600        //   stack.
601        core::arch::asm!(
602            "sub sp, sp, #16",
603            "str {a}, [sp]",
604            "blr {f}",
605            "add sp, sp, #16",
606            f = in(reg) resolve::textfield_axis_init(),
607            a = in(reg) axis as u64,
608            in("x0") k0, in("x1") k1, in("x2") k2, in("x3") k3,
609            in("x4") b[0], in("x5") b[1], in("x6") b[2], in("x7") b[3],
610            in("x8") result.as_mut_ptr(),
611            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
612            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
613            lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
614            clobber_abi("C"),
615        );
616        wrap_owned(result, meta, wt)
617    }
618}
619
620/// Create a `SecureField` showing a fixed string, which the user cannot edit.
621/// See [`securefield_bound`].
622pub fn securefield(placeholder: &str, value: &str) -> ViewHandle {
623    unsafe { securefield_with_binding(placeholder, &string_binding(value)) }
624}
625
626/// Create a `SecureField` wired to Rust state — typing writes into `state`.
627pub fn securefield_bound(placeholder: &str, state: &super::BoundState<String>) -> ViewHandle {
628    unsafe { securefield_with_binding(placeholder, &binding_words(&super::text_binding(state))) }
629}
630
631/// Shared tail of [`securefield`] and [`securefield_bound`].
632unsafe fn securefield_with_binding(placeholder: &str, b: &[u64; 4]) -> ViewHandle {
633    unsafe {
634        let ss = abi::swift_string(placeholder);
635        let s0 = u64::from_le_bytes(ss[..8].try_into().unwrap());
636        let s1 = u64::from_le_bytes(ss[8..].try_into().unwrap());
637        let (k0, k1, k2, k3) = localized_string_key(s0, s1);
638
639        let (meta, wt, size) = result_type(&SECUREFIELD_T);
640        let mut result = vec![0u8; size];
641        let noop = noop_closure as *const c_void;
642        // SecureField.init(_:text:onCommit:) — one trailing closure on the stack
643        core::arch::asm!(
644            "sub sp, sp, #16",
645            "stp {n}, xzr, [sp]",
646            "blr {f}",
647            "add sp, sp, #16",
648            f = in(reg) resolve::securefield_init(),
649            n = in(reg) noop,
650            in("x0") k0, in("x1") k1, in("x2") k2, in("x3") k3,
651            in("x4") b[0], in("x5") b[1], in("x6") b[2], in("x7") b[3],
652            in("x8") result.as_mut_ptr(),
653            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
654            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
655            lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
656            clobber_abi("C"),
657        );
658        wrap_owned(result, meta, wt)
659    }
660}
661
662/// Create a `Slider` over a closed range.
663pub fn slider(value: f64, lower: f64, upper: f64) -> ViewHandle {
664    unsafe {
665        let mut binding = constant_binding(
666            &value.to_le_bytes(),
667            resolve::double_meta(),
668            mangle::bound(&swift_type!(SwiftUI.Binding), &[mangle::DOUBLE]).as_bytes(),
669        );
670        slider_with_binding(&mut binding, lower, upper)
671    }
672}
673
674/// Shared tail of [`slider`] and `slider_bound`.
675///
676/// # Safety
677/// `binding` must be a `Binding<Double>`.
678pub(crate) unsafe fn slider_with_binding(
679    binding: &mut [u8],
680    lower: f64,
681    upper: f64,
682) -> ViewHandle {
683    unsafe {
684        let double_meta = resolve::double_meta();
685        let bfp_wt =
686            abi::get_view_wt_for_proto(double_meta, resolve::binary_floating_point_proto());
687        assert!(!bfp_wt.is_null(), "Double: BinaryFloatingPoint");
688        let mut range = [0u8; 16];
689        range[..8].copy_from_slice(&lower.to_le_bytes());
690        range[8..].copy_from_slice(&upper.to_le_bytes());
691
692        let (meta, wt, size) = result_type(&SLIDER_T);
693        let mut result = vec![0u8; size];
694        // Slider.init(value:in:onEditingChanged:)
695        //   x0 = @in Binding<V>, x1 = @in ClosedRange<V>,
696        //   x2 = closure fn, x3 = closure ctx,
697        //   x4 = V.Type, x5 = V: BinaryFloatingPoint WT,
698        //   x6 = V.Stride: BinaryFloatingPoint WT, x8 = @out
699        //
700        // `V` is generic, so both the binding and the range arrive by pointer
701        // even though Double is loadable.
702        core::arch::asm!("blr {f}", f = in(reg) resolve::slider_init(),
703            in("x0") binding.as_mut_ptr(),
704            in("x1") range.as_mut_ptr(),
705            in("x2") noop_closure as *const c_void,
706            in("x3") 0u64,
707            in("x4") double_meta,
708            in("x5") bfp_wt,
709            in("x6") bfp_wt,
710            in("x8") result.as_mut_ptr(),
711            lateout("x7") _,
712            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
713            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
714            lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
715            clobber_abi("C"),
716        );
717        wrap_owned(result, meta, wt)
718    }
719}
720
721/// The same, snapping to multiples of `step`.
722///
723/// A slider that moves continuously is the right control for a volume; one that
724/// picks a rating out of five is not, and there was no way to ask for the
725/// second. `step` is `V.Stride` — an *associated* type, so it travels by
726/// address like the binding and the range, even though it is a `Double` by the
727/// time it gets there.
728///
729/// # Safety
730/// `binding` must be a live `Binding<Double>`.
731pub(crate) unsafe fn slider_step_with_binding(
732    binding: &mut [u8],
733    lower: f64,
734    upper: f64,
735    step: f64,
736) -> ViewHandle {
737    unsafe {
738        let double_meta = resolve::double_meta();
739        let bfp_wt =
740            abi::get_view_wt_for_proto(double_meta, resolve::binary_floating_point_proto());
741        assert!(!bfp_wt.is_null(), "Double: BinaryFloatingPoint");
742        let mut range = [0u8; 16];
743        range[..8].copy_from_slice(&lower.to_le_bytes());
744        range[8..].copy_from_slice(&upper.to_le_bytes());
745        let mut step_bytes = step.to_le_bytes();
746
747        let (meta, wt, size) = result_type(&SLIDER_T);
748        let mut result = vec![0u8; size];
749        // Slider.init(value:in:step:onEditingChanged:)
750        //   x0 = @in Binding<V>, x1 = @in ClosedRange<V>, x2 = @in V.Stride,
751        //   x3 = closure fn, x4 = closure ctx, x5 = V.Type,
752        //   x6 = V: BinaryFloatingPoint WT,
753        //   x7 = V.Stride: BinaryFloatingPoint WT, x8 = @out
754        core::arch::asm!("blr {f}", f = in(reg) resolve::slider_step_init(),
755            in("x0") binding.as_mut_ptr(),
756            in("x1") range.as_mut_ptr(),
757            in("x2") step_bytes.as_mut_ptr(),
758            in("x3") noop_closure as *const c_void,
759            in("x4") 0u64,
760            in("x5") double_meta,
761            in("x6") bfp_wt,
762            in("x7") bfp_wt,
763            in("x8") result.as_mut_ptr(),
764            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
765            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
766            lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
767            clobber_abi("C"),
768        );
769        wrap_owned(result, meta, wt)
770    }
771}
772
773// ── Modifiers ──
774
775/// Apply `.padding(_:)`.
776pub fn padding(view: &ViewHandle, amount: f64) -> ViewHandle {
777    unsafe {
778        // Sized from the composed result type, not a constant. While every
779        // receiver was a one-word `AnyView` a fixed 64 was always big enough;
780        // against a concrete receiver the result grows with it.
781        let (_, _, size) = PADDING_T.applied_to(view.meta);
782        let result = abi::call_modifier_d0(
783            resolve::padding_fn(), view.value_ptr(), view.meta, view.wt, amount, size);
784        wrap_modified(result, &PADDING_T, view)
785    }
786}
787
788/// Apply `.opacity(_:)`.
789pub fn opacity(view: &ViewHandle, value: f64) -> ViewHandle {
790    unsafe {
791        let (_, _, size) = OPACITY_T.applied_to(view.meta);
792        let result = abi::call_modifier_d0(
793            resolve::opacity_fn(), view.value_ptr(), view.meta, view.wt, value, size);
794        wrap_modified(result, &OPACITY_T, view)
795    }
796}
797
798/// Apply `.frame(width:height:)`, centred.
799pub fn frame(view: &ViewHandle, width: f64, height: f64) -> ViewHandle {
800    frame_aligned(view, width, height, super::Alignment::Center)
801}
802
803/// Apply `.frame(width:height:alignment:)`.
804pub fn frame_aligned(
805    view: &ViewHandle,
806    width: f64,
807    height: f64,
808    alignment: super::Alignment,
809) -> ViewHandle {
810    frame_optional(view, Some(width), Some(height), alignment)
811}
812
813/// Apply `.frame(width:height:alignment:)` with either dimension left out.
814///
815/// `frame(height: 100)` is not `frame(width: 0, height: 100)` — an omitted
816/// dimension means "take what the parent offers", and the `Optional<CGFloat>`
817/// carries that in its tag: payload then tag, 0 for `.some` and 1 for `.none`.
818pub fn frame_optional(
819    view: &ViewHandle,
820    width: Option<f64>,
821    height: Option<f64>,
822    alignment: super::Alignment,
823) -> ViewHandle {
824    unsafe {
825        let self_storage = view.value_ptr();
826        let (_, _, size) = FRAME_T.applied_to(view.meta);
827        let mut result = vec![0u8; size];
828
829        // Alignment is 16 bytes — `.center` is (horizontal, vertical) = (2, 5).
830        // Passing only the first word leaves vertical alignment as 0, which is
831        // not a valid AlignmentID and traps inside FrameLayoutCommon at layout
832        // time rather than here.
833        let [align_h, align_v] = alignment.words();
834
835        // frame(width:height:alignment:)
836        //   x0 = width payload,  x1 = width tag  (0 = some)
837        //   x2 = height payload, x3 = height tag (0 = some)
838        //   x4, x5 = alignment
839        //   x6 = Self.Type, x7 = Self: View WT, x8 = @out, x20 = self
840        core::arch::asm!("blr {func}",
841            func = in(reg) resolve::frame_fn(),
842            in("x0") width.map_or(0, f64::to_bits),
843            in("x1") u64::from(width.is_none()),
844            in("x2") height.map_or(0, f64::to_bits),
845            in("x3") u64::from(height.is_none()),
846            in("x4") align_h,
847            in("x5") align_v,
848            in("x6") view.meta,
849            in("x7") view.wt,
850            in("x8") result.as_mut_ptr(),
851            in("x20") self_storage,
852            lateout("x9") _, lateout("x10") _, lateout("x11") _,
853            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
854            lateout("x16") _, lateout("x17") _, lateout("lr") _,
855            clobber_abi("C"),
856        );
857        wrap_modified(result, &FRAME_T, view)
858    }
859}
860
861/// Apply `.background(_:)` with a Color.
862pub fn background(view: &ViewHandle, r: f64, g: f64, b: f64, a: f64) -> ViewHandle {
863    unsafe {
864        let func = resolve::bg_fn();
865        let self_storage = view.value_ptr();
866        let mut color_storage: u64 = color_value(r, g, b, a);
867        let (_, _, size) = BG_T.applied_to(view.meta);
868        let mut result = vec![0u8; size];
869        // ASM: x0=@in style, x1=Edge.Set, x2=Self.Type, x3=S.Type, x4=Self:View WT, x5=S:ShapeStyle WT
870        //      x8=result, x20=self
871        core::arch::asm!("blr {func}",
872            func = in(reg) func,
873            in("x0") &mut color_storage as *mut u64, // @in Color
874            in("x1") EDGE_SET_ALL,                   // ignoresSafeAreaEdges: .all
875            in("x2") view.meta,                       // Self.Type
876            in("x3") resolve::color_meta(),           // S.Type (Color)
877            in("x4") view.wt,                         // Self:View WT
878            in("x5") resolve::color_shapestyle_wt(),  // S:ShapeStyle WT
879            in("x8") result.as_mut_ptr(),
880            in("x20") self_storage,
881            lateout("x0") _, lateout("x1") _, lateout("x2") _, lateout("x3") _,
882            lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
883            lateout("x9") _, lateout("x10") _, lateout("x11") _,
884            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
885            lateout("x16") _, lateout("x17") _, lateout("lr") _, clobber_abi("C"),
886        );
887        wrap_modified(result, &BG_T, view)
888    }
889}
890
891/// Apply `.cornerRadius(_:)`.
892pub fn corner_radius(view: &ViewHandle, radius: f64) -> ViewHandle {
893    unsafe {
894        let (_, _, size) = CORNER_T.applied_to(view.meta);
895        let mut result = vec![0u8; size];
896        let self_storage = view.value_ptr();
897        // cornerRadius(_:antialiased:)
898        //   d0 = radius, x0 = antialiased, x1 = Self.Type, x2 = Self: View WT,
899        //   x8 = @out, x20 = self
900        core::arch::asm!("blr {f}", f = in(reg) resolve::corner_radius_fn(),
901            in("d0") radius,
902            in("x0") 1u64,
903            in("x1") view.meta,
904            in("x2") view.wt,
905            in("x8") result.as_mut_ptr(),
906            in("x20") self_storage,
907            lateout("x3") _, lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
908            lateout("x9") _, lateout("x10") _, lateout("x11") _,
909            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
910            lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x21") _,
911            clobber_abi("C"),
912        );
913        wrap_modified(result, &CORNER_T, view)
914    }
915}
916
917/// Apply `.border(_:width:)`.
918/// Apply `.border(_:width:)` with colour components.
919///
920/// **Opaque.** There is no alpha parameter and the colour is built at 1.0, so a
921/// translucent border is not expressible here — use [`border_value`] with a
922/// colour that carries its own alpha. The chainable `View::border` does.
923pub fn border(view: &ViewHandle, r: f64, g: f64, b: f64, width: f64) -> ViewHandle {
924    unsafe { border_value(view, color_value(r, g, b, 1.0), width) }
925}
926
927/// Apply `.border(_:width:)` with a `Color` that is already built.
928///
929/// For one of SwiftUI's named colours: `Color.gray` is semantic and no set of
930/// components spells it. See [`system_color`].
931pub fn border_value(view: &ViewHandle, color: u64, width: f64) -> ViewHandle {
932    unsafe {
933        let c = color;
934        let (_, _, size) = BORDER_T.applied_to(view.meta);
935        let mut result = vec![0u8; size];
936        let self_storage = view.value_ptr();
937        let mut style: u64 = c;
938        // border<S: ShapeStyle>(_:width:)
939        //   x0 = @in style, d0 = width, x1 = Self.Type, x2 = S.Type,
940        //   x3 = Self: View WT, x4 = S: ShapeStyle WT, x8 = @out, x20 = self
941        core::arch::asm!("blr {f}", f = in(reg) resolve::border_fn(),
942            in("x0") &mut style as *mut u64,
943            in("d0") width,
944            in("x1") view.meta,
945            in("x2") resolve::color_meta(),
946            in("x3") view.wt,
947            in("x4") resolve::color_shapestyle_wt(),
948            in("x8") result.as_mut_ptr(),
949            in("x20") self_storage,
950            lateout("x5") _, lateout("x6") _, lateout("x7") _,
951            lateout("x9") _, lateout("x10") _, lateout("x11") _,
952            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
953            lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x21") _,
954            clobber_abi("C"),
955        );
956        wrap_modified(result, &BORDER_T, view)
957    }
958}
959
960swift_statics! {
961    /// One of SwiftUI's own named colours.
962    ///
963    /// These are not RGB triples, which is the trap. `Color.blue` is a semantic
964    /// system colour that resolves through the current appearance; under aqua
965    /// it is around `(0, 0.48, 1)`, nowhere near the literal `(0, 0, 1)` a
966    /// constant in this crate spells. Building a colour from components and
967    /// calling it blue renders a visibly different blue — close enough to look
968    /// right on its own and wrong beside the real thing.
969    pub enum SystemColor {
970        Red = c"$s7SwiftUI5ColorV3redACvgZ",
971        Orange = c"$s7SwiftUI5ColorV6orangeACvgZ",
972        Yellow = c"$s7SwiftUI5ColorV6yellowACvgZ",
973        Green = c"$s7SwiftUI5ColorV5greenACvgZ",
974        Mint = c"$s7SwiftUI5ColorV4mintACvgZ",
975        Teal = c"$s7SwiftUI5ColorV4tealACvgZ",
976        Cyan = c"$s7SwiftUI5ColorV4cyanACvgZ",
977        Blue = c"$s7SwiftUI5ColorV4blueACvgZ",
978        Indigo = c"$s7SwiftUI5ColorV6indigoACvgZ",
979        // `accentColor` mangles `06accentC0` — the `C` substitutes `Color` from
980        // earlier in the symbol, so the name never appears spelled out.
981        Accent = c"$s7SwiftUI5ColorV06accentC0ACvgZ",
982        Purple = c"$s7SwiftUI5ColorV6purpleACvgZ",
983        Pink = c"$s7SwiftUI5ColorV4pinkACvgZ",
984        Brown = c"$s7SwiftUI5ColorV5brownACvgZ",
985        White = c"$s7SwiftUI5ColorV5whiteACvgZ",
986        Gray = c"$s7SwiftUI5ColorV4grayACvgZ",
987        Black = c"$s7SwiftUI5ColorV5blackACvgZ",
988        Clear = c"$s7SwiftUI5ColorV5clearACvgZ",
989        Primary = c"$s7SwiftUI5ColorV7primaryACvgZ",
990        Secondary = c"$s7SwiftUI5ColorV9secondaryACvgZ",
991    }
992}
993
994/// The value behind one of SwiftUI's named colours.
995///
996/// A `Color` is one word, so the static getter returns it in x0 with the
997/// metatype in x20 — no indirect result and no copy.
998pub fn system_color_value(c: SystemColor) -> u64 {
999    unsafe { abi::static_regs(resolve::sym(c.getter()), resolve::color_meta())[0] }
1000}
1001
1002/// Create a view of one of SwiftUI's named colours.
1003pub fn system_color(c: SystemColor) -> ViewHandle {
1004    unsafe {
1005        let buf = system_color_value(c).to_le_bytes();
1006        wrap_buf(&buf, resolve::color_meta(), resolve::color_wt())
1007    }
1008}
1009
1010/// Apply `.frame(maxWidth:maxHeight:alignment:)` with `.infinity` for both.
1011///
1012/// Six `Optional<CGFloat>`s — min, ideal and max for each axis — then an
1013/// `Alignment`, then the two generic words. Each optional is payload and tag,
1014/// so that is sixteen words in all: x0 through x7 carry the first four
1015/// optionals and the remaining eight go on the stack. The generic arguments
1016/// come *after* every formal parameter, which is what makes them spill; putting
1017/// them in x6 and x7 builds a frame out of the metadata pointers.
1018pub fn frame_max_infinity(view: &ViewHandle, alignment: super::Alignment) -> ViewHandle {
1019    unsafe {
1020        let (meta, wt, size) = super::opaque_result_type(
1021            resolve::frame_flexible_oret(),
1022            &[view.meta, view.wt],
1023        );
1024        let mut result = vec![0u8; size];
1025        let this = view.value_ptr();
1026        let [ah, av] = alignment.words();
1027        let inf = f64::INFINITY.to_bits();
1028        // Eight words on the stack, stored from registers rather than copied
1029        // from an array: `sub sp` moves the stack pointer out from under any
1030        // operand Rust might have materialised relative to it, and the crate's
1031        // other spilling call sites all do it this way.
1032        core::arch::asm!(
1033            "sub sp, sp, #64",
1034            "stp {a}, {b}, [sp]",
1035            "stp {c}, {d}, [sp, #16]",
1036            "stp {e}, {g}, [sp, #32]",
1037            "stp {h}, {i}, [sp, #48]",
1038            "blr {f}",
1039            "add sp, sp, #64",
1040            f = in(reg) resolve::frame_flexible_fn(),
1041            a = in(reg) 0u64,  b = in(reg) 1u64,   // idealHeight = nil
1042            c = in(reg) inf,   d = in(reg) 0u64,   // maxHeight = .infinity
1043            e = in(reg) ah,    g = in(reg) av,     // alignment
1044            h = in(reg) view.meta,
1045            i = in(reg) view.wt,
1046            in("x0") 0u64, in("x1") 1u64,      // minWidth = nil
1047            in("x2") 0u64, in("x3") 1u64,      // idealWidth = nil
1048            in("x4") inf,  in("x5") 0u64,      // maxWidth = .infinity
1049            in("x6") 0u64, in("x7") 1u64,      // minHeight = nil
1050            in("x8") result.as_mut_ptr(),
1051            in("x20") this,
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, meta, wt)
1057    }
1058}
1059
1060/// Apply `.frame(maxWidth: .infinity)` — the *width* only.
1061///
1062/// Not [`frame_max_infinity`], which sets both axes. Swift's
1063/// `.frame(maxWidth: .infinity)` leaves `maxHeight` nil, so the view keeps its
1064/// content's height; setting both stretched a card to fill its canvas and moved
1065/// everything in it.
1066///
1067/// Six `Optional<CGFloat>`s — min, ideal and max for each axis — then an
1068/// `Alignment`, then the two generic words. Each optional is payload and tag,
1069/// so that is sixteen words in all: x0 through x7 carry the first four
1070/// optionals and the remaining eight go on the stack. The generic arguments
1071/// come *after* every formal parameter, which is what makes them spill; putting
1072/// them in x6 and x7 builds a frame out of the metadata pointers.
1073pub fn frame_max_width(view: &ViewHandle, alignment: super::Alignment) -> ViewHandle {
1074    unsafe {
1075        let (meta, wt, size) = super::opaque_result_type(
1076            resolve::frame_flexible_oret(),
1077            &[view.meta, view.wt],
1078        );
1079        let mut result = vec![0u8; size];
1080        let this = view.value_ptr();
1081        let [ah, av] = alignment.words();
1082        let inf = f64::INFINITY.to_bits();
1083        // Eight words on the stack, stored from registers rather than copied
1084        // from an array: `sub sp` moves the stack pointer out from under any
1085        // operand Rust might have materialised relative to it, and the crate's
1086        // other spilling call sites all do it this way.
1087        core::arch::asm!(
1088            "sub sp, sp, #64",
1089            "stp {a}, {b}, [sp]",
1090            "stp {c}, {d}, [sp, #16]",
1091            "stp {e}, {g}, [sp, #32]",
1092            "stp {h}, {i}, [sp, #48]",
1093            "blr {f}",
1094            "add sp, sp, #64",
1095            f = in(reg) resolve::frame_flexible_fn(),
1096            a = in(reg) 0u64,  b = in(reg) 1u64,   // idealHeight = nil
1097            c = in(reg) 0u64,  d = in(reg) 1u64,   // maxHeight = nil
1098            e = in(reg) ah,    g = in(reg) av,     // alignment
1099            h = in(reg) view.meta,
1100            i = in(reg) view.wt,
1101            in("x0") 0u64, in("x1") 1u64,      // minWidth = nil
1102            in("x2") 0u64, in("x3") 1u64,      // idealWidth = nil
1103            in("x4") inf,  in("x5") 0u64,      // maxWidth = .infinity
1104            in("x6") 0u64, in("x7") 1u64,      // minHeight = nil
1105            in("x8") result.as_mut_ptr(),
1106            in("x20") this,
1107            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
1108            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
1109            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
1110        );
1111        wrap_owned(result, meta, wt)
1112    }
1113}
1114
1115/// `TextEditor(text:)` — a multi-line editable field.
1116///
1117/// The whole argument list is one `Binding<String>`, which is four words and
1118/// travels in x0 through x3.
1119pub fn text_editor(value: &str) -> ViewHandle {
1120    unsafe {
1121        let b = string_binding(value);
1122        let (meta, wt, size) = result_type(&TEXT_EDITOR_T);
1123        let mut result = vec![0u8; size];
1124        core::arch::asm!("blr {f}", f = in(reg) resolve::text_editor_init(),
1125            in("x0") b[0], in("x1") b[1], in("x2") b[2], in("x3") b[3],
1126            in("x8") result.as_mut_ptr(),
1127            lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
1128            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
1129            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
1130            lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
1131            clobber_abi("C"),
1132        );
1133        wrap_owned(result, meta, wt)
1134    }
1135}
1136
1137/// `Link(_:destination:)` — a label that opens a URL.
1138///
1139/// A `LocalizedStringKey` in x0 through x3, then the `URL` by address. The
1140/// crate builds URLs as `Optional<URL>` because that is what `init?(string:)`
1141/// returns; a `.some` keeps its payload at offset zero, so the same buffer
1142/// serves where a bare `URL` is wanted.
1143pub fn link(title: &str, destination: &str) -> ViewHandle {
1144    unsafe {
1145        let ss = abi::swift_string(title);
1146        let s0 = u64::from_le_bytes(ss[..8].try_into().expect("lo"));
1147        let s1 = u64::from_le_bytes(ss[8..].try_into().expect("hi"));
1148        let (k0, k1, k2, k3) = localized_string_key(s0, s1);
1149        let mut url = super::optional_url(Some(destination));
1150
1151        let (meta, wt, size) = result_type(&LINK_T);
1152        let mut result = vec![0u8; size];
1153        core::arch::asm!("blr {f}", f = in(reg) resolve::link_init(),
1154            in("x0") k0, in("x1") k1, in("x2") k2, in("x3") k3,
1155            in("x4") url.as_mut_ptr(),
1156            in("x8") result.as_mut_ptr(),
1157            lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x9") _,
1158            lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
1159            lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
1160            lateout("lr") _, lateout("x20") _, lateout("x21") _, clobber_abi("C"),
1161        );
1162        wrap_owned(result, meta, wt)
1163    }
1164}
1165
1166/// `.opacity(_:)`, taking `Color`'s own overload when the receiver is a `Color`.
1167///
1168/// `Color.opacity(_:)` returns `Color` — the same shape as `Text.bold()` and
1169/// `Image.resizable()`, and the reason Apple's `Color.primary.opacity(0.1)`
1170/// types as a plain `Color` where the `View` extension gives
1171/// `ModifiedContent<Color, _OpacityEffect>`. Both render identically; only one
1172/// is the type the listing has.
1173pub fn view_opacity(view: &ViewHandle, value: f64) -> ViewHandle {
1174    if crate::diag::type_name(view.meta) != "SwiftUI.Color" {
1175        return opacity(view, value);
1176    }
1177    unsafe {
1178        // A `Color` is one reference, so its value is one word.
1179        let word = (view.value_ptr() as *const u64).read_unaligned();
1180        let faded = color_opacity(word, value);
1181        wrap_buf(&faded.to_le_bytes(), view.meta, view.wt)
1182    }
1183}
1184
1185/// One piece of an interpolated `Text`.
1186#[derive(Clone, Copy, Debug)]
1187pub enum TextPart<'a> {
1188    /// Literal text, kept as a literal segment of the key.
1189    Literal(&'a str),
1190    /// A value interpolated with `%lld`, the specifier Swift uses for `Int`.
1191    Int(i64),
1192    /// A value interpolated with `%lf`, Swift's specifier for `Double`.
1193    Double(f64),
1194    /// A string, through the overload that takes one whole.
1195    ///
1196    /// Not a specifier: `appendInterpolation(_ string: String)` is its own
1197    /// method, and the value goes in as text rather than as a placeholder a
1198    /// translation could reorder.
1199    Str(&'a str),
1200}
1201
1202/// Anything that can be interpolated into a [`text_interpolated`].
1203///
1204/// So a caller writes the value and not the variant — and so [`crate::ltext!`]
1205/// can accept whatever the format string was given.
1206pub trait Interpolatable {
1207    /// This value as a part.
1208    fn as_part(&self) -> TextPart<'_>;
1209}
1210
1211impl Interpolatable for i64 {
1212    fn as_part(&self) -> TextPart<'_> {
1213        TextPart::Int(*self)
1214    }
1215}
1216
1217impl Interpolatable for i32 {
1218    fn as_part(&self) -> TextPart<'_> {
1219        TextPart::Int(i64::from(*self))
1220    }
1221}
1222
1223impl Interpolatable for usize {
1224    fn as_part(&self) -> TextPart<'_> {
1225        TextPart::Int(*self as i64)
1226    }
1227}
1228
1229impl Interpolatable for f64 {
1230    fn as_part(&self) -> TextPart<'_> {
1231        TextPart::Double(*self)
1232    }
1233}
1234
1235impl Interpolatable for str {
1236    fn as_part(&self) -> TextPart<'_> {
1237        TextPart::Str(self)
1238    }
1239}
1240
1241impl Interpolatable for String {
1242    fn as_part(&self) -> TextPart<'_> {
1243        TextPart::Str(self)
1244    }
1245}
1246
1247impl<T: Interpolatable + ?Sized> Interpolatable for &T {
1248    fn as_part(&self) -> TextPart<'_> {
1249        (**self).as_part()
1250    }
1251}
1252
1253/// `Text("Item \(i)")` — a `Text` built through SwiftUI's own interpolation.
1254///
1255/// [`text()`] takes a Rust `String` and hands SwiftUI a *literal* key. That draws
1256/// the same pixels, and it is a different value: an interpolated
1257/// `LocalizedStringKey` keeps its literal segments and its arguments apart,
1258/// with a format specifier for each, which is what lets a translation reorder
1259/// them. Formatting in Rust throws that structure away before SwiftUI sees it.
1260///
1261/// ```no_run
1262/// # use swiftui_native::views::{self, TextPart};
1263/// // The same value `Text("Item \(i)")` builds.
1264/// views::text_interpolated(&[TextPart::Literal("Item "), TextPart::Int(3)]);
1265/// ```
1266pub fn text_interpolated(parts: &[TextPart<'_>]) -> ViewHandle {
1267    unsafe {
1268        let meta = abi::resolve_type_by_mangled_name(b"7SwiftUI18LocalizedStringKeyV0D13InterpolationV");
1269        assert!(!meta.is_null(), "LocalizedStringKey.StringInterpolation metadata");
1270
1271        // The two hints Swift passes. Only the literal segments count towards
1272        // the capacity; everything interpolated is an argument, whatever its
1273        // type, and counts towards the other number.
1274        let literal_capacity: i64 = parts
1275            .iter()
1276            .map(|p| match p {
1277                TextPart::Literal(s) => s.len() as i64,
1278                _ => 0,
1279            })
1280            .sum();
1281        let interpolation_count = parts
1282            .iter()
1283            .filter(|p| !matches!(p, TextPart::Literal(_)))
1284            .count() as i64;
1285
1286        let mut interp = vec![0u8; abi::type_bytes(meta)];
1287        // `init(literalCapacity:interpolationCount:)` — the two numbers are a
1288        // hint for the buffer, not a constraint, but Swift passes the real
1289        // ones and a value comparison notices the difference.
1290        core::arch::asm!("blr {f}", f = in(reg) resolve::lsk_interp_init(),
1291            in("x0") literal_capacity,
1292            in("x1") interpolation_count,
1293            in("x8") interp.as_mut_ptr(),
1294            lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
1295            lateout("x6") _, lateout("x7") _, lateout("x9") _, lateout("x10") _,
1296            lateout("x11") _, lateout("x12") _, lateout("x13") _, lateout("x14") _,
1297            lateout("x15") _, lateout("x16") _, lateout("x17") _, lateout("lr") _,
1298            lateout("x20") _, lateout("x21") _, clobber_abi("C"),
1299        );
1300
1301        for part in parts {
1302            match *part {
1303                TextPart::Literal(s) => {
1304                    let ss = abi::swift_string(s);
1305                    let w0 = u64::from_le_bytes(ss[..8].try_into().expect("lo"));
1306                    let w1 = u64::from_le_bytes(ss[8..].try_into().expect("hi"));
1307                    // `appendLiteral(_: String)`: the string in x0/x1 and the
1308                    // interpolation as `inout self` in x20.
1309                    core::arch::asm!("blr {f}", f = in(reg) resolve::lsk_append_literal(),
1310                        in("x0") w0, in("x1") w1,
1311                        in("x20") interp.as_mut_ptr(),
1312                        lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
1313                        lateout("x6") _, lateout("x7") _, lateout("x8") _, lateout("x9") _,
1314                        lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
1315                        lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
1316                        lateout("lr") _, lateout("x21") _, clobber_abi("C"),
1317                    );
1318                }
1319                TextPart::Str(text) => {
1320                    let ss = abi::swift_string(text);
1321                    let w0 = u64::from_le_bytes(ss[..8].try_into().expect("lo"));
1322                    let w1 = u64::from_le_bytes(ss[8..].try_into().expect("hi"));
1323                    // `appendInterpolation(_ string: String)` — no specifier,
1324                    // no metatype: a `String` goes in whole.
1325                    core::arch::asm!("blr {f}", f = in(reg) resolve::lsk_append_string(),
1326                        in("x0") w0, in("x1") w1,
1327                        in("x20") interp.as_mut_ptr(),
1328                        lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
1329                        lateout("x6") _, lateout("x7") _, lateout("x8") _, lateout("x9") _,
1330                        lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
1331                        lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
1332                        lateout("lr") _, lateout("x21") _, clobber_abi("C"),
1333                    );
1334                }
1335                TextPart::Double(v) => {
1336                    append_specified(
1337                        &mut interp,
1338                        &v.to_le_bytes(),
1339                        "%lf",
1340                        resolve::double_meta(),
1341                        resolve::double_format_specifiable_wt(),
1342                    );
1343                }
1344                TextPart::Int(v) => {
1345                    append_specified(
1346                        &mut interp,
1347                        &v.to_le_bytes(),
1348                        "%lld",
1349                        resolve::int_meta(),
1350                        resolve::int_format_specifiable_wt(),
1351                    );
1352                }
1353            }
1354        }
1355
1356        // `LocalizedStringKey.init(stringInterpolation:)` — the interpolation
1357        // by address, the key back in x0–x3.
1358        let (k0, k1, k2, k3): (u64, u64, u64, u64);
1359        core::arch::asm!("blr {f}", f = in(reg) resolve::lsk_from_interp(),
1360            in("x0") interp.as_mut_ptr(),
1361            lateout("x0") k0, lateout("x1") k1, lateout("x2") k2, lateout("x3") k3,
1362            lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
1363            lateout("x8") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
1364            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
1365            lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x20") _,
1366            lateout("x21") _, clobber_abi("C"),
1367        );
1368
1369        text_from_key(k0, k1, k2, k3)
1370    }
1371}
1372
1373/// `appendInterpolation(_:specifier:)` for one `_FormatSpecifiable` value.
1374///
1375/// `SwiftUI.formatSpecifier(T.self)` is `@inlinable` and exports no symbol, so
1376/// the specifier string is passed in — `"%lld"` for `Int`, `"%lf"` for
1377/// `Double`, which is what the inlined body returns and what the compiler's
1378/// own IR has as a constant.
1379///
1380/// # Safety
1381/// `bytes` must be a live value of `meta`, conforming through `wt`.
1382unsafe fn append_specified(
1383    interp: &mut [u8],
1384    bytes: &[u8],
1385    specifier: &str,
1386    meta: *const c_void,
1387    wt: *const c_void,
1388) {
1389    unsafe {
1390        let spec = abi::swift_string(specifier);
1391        let sp0 = u64::from_le_bytes(spec[..8].try_into().expect("lo"));
1392        let sp1 = u64::from_le_bytes(spec[8..].try_into().expect("hi"));
1393        let mut value = bytes.to_vec();
1394        // The value by address, because the parameter is generic; then the
1395        // specifier, then `T.Type` and `T: _FormatSpecifiable`.
1396        core::arch::asm!("blr {f}", f = in(reg) resolve::lsk_append_interp(),
1397            in("x0") value.as_mut_ptr(),
1398            in("x1") sp0, in("x2") sp1,
1399            in("x3") meta,
1400            in("x4") wt,
1401            in("x20") interp.as_mut_ptr(),
1402            lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x8") _,
1403            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
1404            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
1405            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
1406        );
1407    }
1408}
1409
1410/// `Text.init(_:tableName:bundle:comment:)` over a built key.
1411///
1412/// # Safety
1413/// The four words must be a `+1` `LocalizedStringKey`.
1414pub(crate) unsafe fn text_from_key(k0: u64, k1: u64, k2: u64, k3: u64) -> ViewHandle {
1415    unsafe {
1416        let (t0, t1, t2, t3): (u64, u64, u64, u64);
1417        core::arch::asm!(
1418            "sub sp, sp, #32",
1419            "stp xzr, xzr, [sp]",
1420            "stp xzr, xzr, [sp, #16]",
1421            "blr {f}",
1422            "add sp, sp, #32",
1423            f = in(reg) resolve::text_lsk_init(),
1424            inout("x0") k0 => t0, inout("x1") k1 => t1,
1425            inout("x2") k2 => t2, inout("x3") k3 => t3,
1426            in("x4") 0u64, in("x5") 0u64, in("x6") 0u64, in("x7") 0u64,
1427            in("x20") resolve::text_meta(),
1428            lateout("x8") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
1429            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
1430            lateout("x16") _, lateout("x17") _, lateout("lr") _, clobber_abi("C"),
1431        );
1432        let mut buf = [0u8; 32];
1433        buf[..8].copy_from_slice(&t0.to_le_bytes());
1434        buf[8..16].copy_from_slice(&t1.to_le_bytes());
1435        buf[16..24].copy_from_slice(&t2.to_le_bytes());
1436        buf[24..32].copy_from_slice(&t3.to_le_bytes());
1437        wrap_buf(&buf, resolve::text_meta(), resolve::text_wt())
1438    }
1439}