swiftui-native 0.0.1

SwiftUI from pure Rust — no Swift bridge, no shim, only swift-runtime calls
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
//! Two-way bindings — Rust-owned state.

use super::*;
use crate::abi;
use crate::resolve;
use core::ffi::c_void;
use crate::mangle::{self, swift_type};

// ── Two-way bindings — Rust-owned state ──

/// Where a persistent [`BoundState`] also writes on every set.
pub type Persist<T> = std::sync::Arc<dyn Fn(&T) + Send + Sync>;

/// Rust-side state a SwiftUI control can both read and write.
///
/// This is not `@State`. SwiftUI's own property wrappers need the framework to
/// reach into a Swift type's storage through reflection metadata, which a Rust
/// type does not have. What this gives instead is the half that matters from
/// Rust: a real `Binding<Value>` backed by Rust closures, so a control renders
/// from Rust state and writes edits straight back into it.
///
/// Redisplay is driven explicitly, because SwiftUI cannot observe a Rust value:
/// a `Binding`'s getter is read only when something else causes the body to be
/// evaluated. Register [`on_write`] to be told when a control writes, and
/// answer it by bumping a `@State` ([`crate::state::set_state`]) or by handing
/// the hosting controller a new tree ([`crate::HostingView::set_root`]).
/// Without that, an edit moves the control and updates the Rust value while a
/// `Text` showing that value stays as it was.
pub struct BoundState<T> {
    value: std::sync::Arc<std::sync::Mutex<T>>,
    /// Where to also write on [`set`](Self::set), if this state is persistent.
    ///
    /// Held here rather than in a second type so that every control taking a
    /// `&BoundState<T>` — `Toggle`, `Slider`, `TextField`, all of them —
    /// persists with no change of its own.
    persist: Option<Persist<T>>,
}

impl<T: Clone> BoundState<T> {
    pub fn new(initial: T) -> Self {
        Self {
            value: std::sync::Arc::new(std::sync::Mutex::new(initial)),
            persist: None,
        }
    }

    pub fn get(&self) -> T {
        self.value.lock().unwrap().clone()
    }

    pub fn set(&self, v: T) {
        if let Some(write) = &self.persist {
            write(&v);
        }
        *self.value.lock().unwrap() = v;
    }

    /// Is this state written through to `UserDefaults`?
    pub fn is_persistent(&self) -> bool {
        self.persist.is_some()
    }
}

impl<T: crate::defaults::Persisted> BoundState<T> {
    /// State backed by `UserDefaults`, which is what `@AppStorage` is for.
    ///
    /// The stored value wins over `default` when there is one — including a
    /// stored `false` or `0`, which is why [`defaults::has`](crate::defaults::has)
    /// exists rather than leaning on the typed readers.
    ///
    /// Writes go through on [`set`](Self::set), so a control bound to this
    /// persists as the user changes it and comes back to the same value in the
    /// next process. SwiftUI is not involved in the storage at all: the value
    /// lives in Rust and is mirrored to the same `UserDefaults` that
    /// `@AppStorage` would have used.
    pub fn persistent(key: &str, default: T) -> Self {
        let initial = <T as crate::defaults::Persisted>::load(key, default);
        let owned = key.to_owned();
        Self {
            value: std::sync::Arc::new(std::sync::Mutex::new(initial)),
            persist: Some(std::sync::Arc::new(move |v: &T| {
                <T as crate::defaults::Persisted>::store(&owned, v);
            })),
        }
    }
}

impl<T> Clone for BoundState<T> {
    fn clone(&self) -> Self {
        Self { value: self.value.clone(), persist: self.persist.clone() }
    }
}

/// Getter/setter pair backing one binding, kept alive for SwiftUI's benefit.
pub(crate) struct BindingHooks {
    get: Box<dyn Fn(*mut u8)>,
    set: Box<dyn Fn(*const u8)>,
}

pub(crate) unsafe extern "C" fn binding_get_body(out: *mut u8, payload: *mut c_void) {
    let hooks = unsafe { &*(payload as *const BindingHooks) };
    (hooks.get)(out);
}

pub(crate) unsafe extern "C" fn binding_set_body(value: *const u8, payload: *mut c_void) {
    let hooks = unsafe { &*(payload as *const BindingHooks) };
    (hooks.set)(value);
    // A control redraws itself after an edit, but anything *else* that reads the
    // same Rust value — a label showing the number, a view two sections away —
    // will not, because SwiftUI never saw a change it tracks. This is where an
    // application says "that write mattered"; see [`on_write`].
    if let Some(f) = ON_WRITE.get() {
        f();
    }
}

/// Called after any bound control writes back into Rust.
static ON_WRITE: std::sync::OnceLock<Box<dyn Fn() + Send + Sync>> =
    std::sync::OnceLock::new();

/// Register what to do when a bound control writes.
///
/// Rust owns the state, so Rust decides when the interface is stale. The usual
/// implementation bumps a `@State` — see [`crate::state::set_state`] — which is
/// the one thing SwiftUI does track, and re-runs the body against whatever Rust
/// now holds. Without this, editing a `Slider` moves the thumb and updates the
/// Rust value while a `Text` showing that value stays as it was.
///
/// The first registration wins; later calls are ignored.
pub fn on_write(f: impl Fn() + Send + Sync + 'static) {
    let _ = ON_WRITE.set(Box::new(f));
}

/// Build a `Binding<Value>` from Rust getter and setter closures.
///
/// The hooks are leaked: SwiftUI keeps the binding for as long as the view
/// exists and calls back into them on every read and every edit.
pub(crate) unsafe fn binding_get_set(
    value_meta: *const c_void,
    binding_mangled: &[u8],
    get: Box<dyn Fn(*mut u8)>,
    set: Box<dyn Fn(*const u8)>,
) -> Vec<u8> {
    unsafe {
        let hooks = Box::leak(Box::new(BindingHooks { get, set })) as *mut BindingHooks;
        let get_ctx = Box::leak(Box::new(abi::ClosureCtx::new(
            binding_get_body,
            hooks as *mut c_void,
        ))) as *mut abi::ClosureCtx;
        let set_ctx = Box::leak(Box::new(abi::ValueClosureCtx::new(
            binding_set_body,
            hooks as *mut c_void,
        ))) as *mut abi::ValueClosureCtx;

        let binding_meta = abi::resolve_type_by_mangled_name(binding_mangled);
        assert!(!binding_meta.is_null(), "Binding metadata");
        let n = abi::type_stride(binding_meta).max(abi::type_size(binding_meta));
        let mut out = vec![0u8; n];
        // Binding.init(get:set:)
        //   x0/x1 = get closure, x2/x3 = set closure,
        //   x4 = Value.Type, x8 = @out Binding
        core::arch::asm!("blr {f}", f = in(reg) resolve::binding_get_set(),
            in("x0") abi::swift_closure_thunk as *const c_void,
            in("x1") get_ctx,
            in("x2") abi::value_closure_thunk as *const c_void,
            in("x3") set_ctx,
            in("x4") value_meta,
            in("x8") out.as_mut_ptr(),
            lateout("x5") _, lateout("x6") _, lateout("x7") _,
            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
            lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
            clobber_abi("C"),
        );
        out
    }
}

/// A `Toggle` wired to Rust state: it renders the current value and writes
/// changes straight back.
pub fn toggle_bound(label: &str, state: &BoundState<bool>) -> ViewHandle {
    unsafe {
        let g = state.clone();
        let sset = state.clone();
        let binding = binding_get_set(
            resolve::bool_meta(),
            mangle::bound(&swift_type!(SwiftUI.Binding), &[mangle::BOOL]).as_bytes(),
            Box::new(move |out| *out = u8::from(g.get())),
            Box::new(move |v| sset.set(*v != 0)),
        );
        toggle_with_binding(label, &binding)
    }
}

/// `Toggle(label, isOn: $flag)` — a toggle driven directly by a real
/// `@AppStorage`.
///
/// No Rust state sits in between: the binding is the one the property wrapper
/// projects, so flipping the toggle writes through `UserDefaults` and re-runs
/// the owning view's body. [`toggle_bound`] is the counterpart for state Rust
/// owns.
pub fn toggle_app_storage(label: &str, stored: &crate::state::StoredView<bool>) -> ViewHandle {
    unsafe { toggle_with_binding(label, &stored.binding()) }
}

/// `Slider(value: $volume, in: lower...upper)` over a real `@AppStorage`.
pub fn slider_app_storage(
    stored: &crate::state::StoredView<f64>,
    lower: f64,
    upper: f64,
) -> ViewHandle {
    unsafe {
        let mut binding = stored.binding();
        super::slider_with_binding(&mut binding, lower, upper)
    }
}

/// `Stepper(label, value: $count, in: lower...upper, step:)` over a real
/// `@AppStorage`.
pub fn stepper_app_storage(
    label: &str,
    stored: &crate::state::StoredView<f64>,
    lower: f64,
    upper: f64,
    step: f64,
) -> ViewHandle {
    unsafe {
        let mut binding = stored.binding();
        super::stepper_with_binding(label, &mut binding, lower, upper, step)
    }
}

/// A `Binding<T>` over Rust-owned state, for a value whose Swift representation
/// is a fixed run of bytes.
///
/// Every bound control needs the same pair of closures — read the Rust value
/// into the out-pointer, write the incoming bytes back — differing only in how
/// many bytes and how to convert them. This is that pair, once.
///
/// # Safety
/// `meta` and `mangled` must describe the same type as `N` bytes of `T`.
pub(crate) unsafe fn scalar_binding<T, const N: usize>(
    state: &BoundState<T>,
    meta: *const c_void,
    mangled: &[u8],
    to_bytes: fn(&T) -> [u8; N],
    from_bytes: fn([u8; N]) -> T,
) -> Vec<u8>
where
    T: Clone + Send + 'static,
{
    let g = state.clone();
    let s = state.clone();
    unsafe {
        binding_get_set(
            meta,
            mangled,
            Box::new(move |out| {
                let v = to_bytes(&g.get());
                core::ptr::copy_nonoverlapping(v.as_ptr(), out, N);
            }),
            Box::new(move |v| {
                let mut b = [0u8; N];
                core::ptr::copy_nonoverlapping(v, b.as_mut_ptr(), N);
                s.set(from_bytes(b));
            }),
        )
    }
}

/// The `Binding<Bool>` every presentation modifier takes for `isPresented`.
///
/// # Safety
/// Builds a binding; see [`scalar_binding`].
pub(crate) unsafe fn bool_binding_of(state: &BoundState<bool>) -> Vec<u8> {
    unsafe {
        scalar_binding::<bool, 1>(
            state,
            resolve::bool_meta(),
            mangle::bound(&swift_type!(SwiftUI.Binding), &[mangle::BOOL]).as_bytes(),
            |v| [u8::from(*v)],
            |b| b[0] != 0,
        )
    }
}

/// A `Binding<Double>`, for sliders, steppers and dates.
///
/// # Safety
/// Builds a binding; see [`scalar_binding`].
pub(crate) unsafe fn f64_binding_of(state: &BoundState<f64>) -> Vec<u8> {
    unsafe {
        scalar_binding::<f64, 8>(
            state,
            resolve::double_meta(),
            mangle::bound(&swift_type!(SwiftUI.Binding), &[mangle::DOUBLE]).as_bytes(),
            |v| v.to_le_bytes(),
            f64::from_le_bytes,
        )
    }
}

/// A `Binding<Int>`, for a `Picker`'s selection.
///
/// # Safety
/// Builds a binding; see [`scalar_binding`].
pub(crate) unsafe fn i64_binding_of(state: &BoundState<i64>) -> Vec<u8> {
    unsafe {
        scalar_binding::<i64, 8>(
            state,
            resolve::int_meta(),
            mangle::bound(&swift_type!(SwiftUI.Binding), &[mangle::INT]).as_bytes(),
            |v| v.to_le_bytes(),
            i64::from_le_bytes,
        )
    }
}

/// A `Slider` wired to Rust state: it renders the current value and writes
/// every drag straight back.
///
/// [`super::slider`] builds a `Binding.constant`, which SwiftUI treats as
/// read-only — the thumb cannot move, however well the touch arrives. A slider
/// the user can actually drag needs a binding with a setter, which is this.
pub fn slider_bound(state: &BoundState<f64>, lower: f64, upper: f64) -> ViewHandle {
    unsafe {
        let mut binding = f64_binding_of(state);
        super::slider_with_binding(&mut binding, lower, upper)
    }
}

/// A `Slider` that snaps to multiples of `step`.
///
/// A continuous slider is right for a volume and wrong for a rating out of
/// five; only the continuous one was bound.
pub fn slider_step_bound(
    state: &BoundState<f64>,
    lower: f64,
    upper: f64,
    step: f64,
) -> ViewHandle {
    unsafe {
        let mut binding = f64_binding_of(state);
        super::slider_step_with_binding(&mut binding, lower, upper, step)
    }
}

/// A `Stepper` wired to Rust state: `−` and `+` write straight back.
///
/// Same reason as [`slider_bound`] — [`super::stepper`] builds a
/// `Binding.constant`, and SwiftUI will not let a constant be stepped.
pub fn stepper_bound(
    label: &str,
    state: &BoundState<f64>,
    lower: f64,
    upper: f64,
    step: f64,
) -> ViewHandle {
    unsafe {
        let mut binding = f64_binding_of(state);
        super::stepper_with_binding(label, &mut binding, lower, upper, step)
    }
}

/// Shared tail of `toggle` and `toggle_bound`.
pub(crate) unsafe fn toggle_with_binding(label: &str, binding: &[u8]) -> ViewHandle {
    unsafe {
        let ss = abi::swift_string(label);
        let s0 = u64::from_le_bytes(ss[..8].try_into().unwrap());
        let s1 = u64::from_le_bytes(ss[8..].try_into().unwrap());
        let (k0, k1, k2, k3) = localized_string_key(s0, s1);

        let b0 = u64::from_le_bytes(binding[0..8].try_into().unwrap());
        let b1 = u64::from_le_bytes(binding[8..16].try_into().unwrap());
        let b2 = binding[16] as u64;

        let (meta, wt, size) = result_type(&TOGGLE_T);
        let mut result = vec![0u8; size];
        core::arch::asm!("blr {f}", f = in(reg) resolve::toggle_init(),
            in("x0") k0, in("x1") k1, in("x2") k2, in("x3") k3,
            in("x4") b0, in("x5") b1, in("x6") b2,
            in("x8") result.as_mut_ptr(),
            in("x20") meta,
            lateout("x7") _,
            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
        );
        wrap_owned(result, meta, wt)
    }
}

/// Write through a `Binding<Bool>`, exactly as a control does when the user
/// interacts with it. Used to prove the write-back path end to end.
/// Write through a `Binding<T>` the way a control does — SwiftUI's own
/// `wrappedValue` setter, reached through its dispatch thunk.
///
/// This is what makes a bound control testable without a touch: build the
/// control's binding, write through it, and see whether the Rust value moved.
///
/// # Safety
/// `value` must be the little-endian bytes of the type `mangled` names, and
/// `binding` must be a `Binding` of that type.
pub unsafe fn binding_write(binding: &mut [u8], value: &mut [u8], mangled: &[u8]) {
    unsafe {
        let meta = abi::resolve_type_by_mangled_name(mangled);
        assert!(!meta.is_null(), "binding type metadata");
        core::arch::asm!("blr {f}", f = in(reg) resolve::binding_wrapped_set(),
            in("x0") value.as_mut_ptr(),
            in("x1") meta,
            in("x20") binding.as_mut_ptr(),
            lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
            lateout("x6") _, lateout("x7") _, lateout("x8") _, lateout("x9") _,
            lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
            lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
            lateout("lr") _, lateout("x21") _, clobber_abi("C"),
        );
    }
}

pub fn binding_write_bool(binding: &mut [u8], value: bool) {
    unsafe {
        let mut v = [u8::from(value)];
        let meta = abi::resolve_type_by_mangled_name(mangle::bound("7SwiftUI7BindingV", &[mangle::BOOL]).as_bytes());
        core::arch::asm!("blr {f}", f = in(reg) resolve::binding_wrapped_set(),
            in("x0") v.as_mut_ptr(),
            in("x1") meta,
            in("x20") binding.as_mut_ptr(),
            lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
            lateout("x6") _, lateout("x7") _, lateout("x8") _, lateout("x9") _,
            lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
            lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
            lateout("lr") _, lateout("x21") _, clobber_abi("C"),
        );
    }
}

/// Build a `Binding<Bool>` over Rust state, for direct write-back testing.
/// Build a `Binding<String>` backed by Rust state.
///
/// Named for the binding rather than the type: `string_binding` already means
/// something else here — the four words of a `LocalizedStringKey`.
///
/// The getter hands SwiftUI a freshly built Swift `String` each time it asks;
/// the setter copies the incoming one out without touching its storage, since
/// the value arrives borrowed.
pub fn text_binding(state: &BoundState<String>) -> Vec<u8> {
    let g = state.clone();
    let s = state.clone();
    unsafe {
        binding_get_set(
            resolve::string_meta(),
            mangle::bound(&swift_type!(SwiftUI.Binding), &[mangle::STRING]).as_bytes(),
            Box::new(move |out| {
                let bytes = abi::swift_string(&g.get());
                core::ptr::copy_nonoverlapping(bytes.as_ptr(), out, 16);
            }),
            Box::new(move |v| {
                let mut bytes = [0u8; 16];
                core::ptr::copy_nonoverlapping(v, bytes.as_mut_ptr(), 16);
                s.set(abi::read_swift_string(bytes));
            }),
        )
    }
}

/// Build a `Binding<Double>` backed by Rust state.
pub fn f64_binding(state: &BoundState<f64>) -> Vec<u8> {
    let g = state.clone();
    let s = state.clone();
    unsafe {
        binding_get_set(
            resolve::double_meta(),
            mangle::bound(&swift_type!(SwiftUI.Binding), &[mangle::DOUBLE]).as_bytes(),
            Box::new(move |out| {
                let v = g.get();
                core::ptr::copy_nonoverlapping(v.to_le_bytes().as_ptr(), out, 8);
            }),
            Box::new(move |v| {
                let mut b = [0u8; 8];
                core::ptr::copy_nonoverlapping(v, b.as_mut_ptr(), 8);
                s.set(f64::from_le_bytes(b));
            }),
        )
    }
}

/// Build a `Binding<Int>` backed by Rust state.
/// A `Binding<Int?>` over Rust state.
///
/// Built for a selectable `List`, which is **not yet bound**. Four approaches
/// have failed, and what is known now narrows it a long way:
///
/// Calling `List(_:id:selection:rowContent:)` crashes under three argument
/// layouts — the selection in four registers (it measures thirty-two bytes and
/// `Binding` is `@frozen`, so direct passing is what the ABI rules imply), the
/// selection by address, and the selection in registers with `Self.Type` in
/// swiftself. What that initialiser wants is still unknown.
///
/// It does not have to be called, though. `List<Never, C>` and
/// `List<SelectionValue, C>` lay out identically — both `[0, 32, 80, 88]`, read
/// from each type's metadata — and only `selection` depends on
/// `SelectionValue`. Building the list through the initialiser that *does*
/// work and copying its other three fields across produces a value that builds,
/// dumps and renders correctly, **as long as the selection is left nil**.
/// Writing a live binding into it makes the value fault the first time
/// anything copies it.
///
/// Ruled out as causes of that: the optional tag (`Binding<Int?>?` uses a spare
/// bit, so writing `.some` is a no-op and changes nothing either way); the
/// location not being a real object (the binding comes from SwiftUI's own
/// `Binding.init(get:set:)`); and the field offsets, which are read rather than
/// assumed.
///
/// This binding itself is sound and checked: thirty-two bytes with a live
/// location and the value in the third word.
///
/// "Nothing selected" has to be representable, which is why the bound value is
/// an optional rather than a sentinel integer. `Int` has no spare bits, so
/// `Int?` is the eight bytes plus a tag byte, and the tag is what says which it
/// is; [`abi::store_optional_tag`] writes it rather than this guessing at the
/// layout.
pub fn optional_i64_binding(state: &BoundState<Option<i64>>) -> Vec<u8> {
    let g = state.clone();
    let s = state.clone();
    unsafe {
        let payload = resolve::int_meta();
        let opt = abi::resolve_type_by_mangled_name(b"SiSg");
        assert!(!opt.is_null(), "Optional<Int> metadata");
        let width = abi::type_bytes(opt);
        binding_get_set(
            opt,
            mangle::bound(&swift_type!(SwiftUI.Binding), &[&mangle::optional(mangle::INT)])
                .as_bytes(),
            Box::new(move |out| {
                let slot = core::slice::from_raw_parts_mut(out, width);
                slot.fill(0);
                match g.get() {
                    Some(v) => {
                        slot[..8].copy_from_slice(&v.to_le_bytes());
                        abi::store_optional_tag(slot, payload, true);
                    }
                    None => abi::store_optional_tag(slot, payload, false),
                }
            }),
            Box::new(move |v| {
                let slot = core::slice::from_raw_parts(v, width);
                let present = abi::read_optional_tag(slot, payload);
                s.set(present.then(|| {
                    let mut b = [0u8; 8];
                    b.copy_from_slice(&slot[..8]);
                    i64::from_le_bytes(b)
                }));
            }),
        )
    }
}

pub fn i64_binding(state: &BoundState<i64>) -> Vec<u8> {
    let g = state.clone();
    let s = state.clone();
    unsafe {
        binding_get_set(
            resolve::int_meta(),
            mangle::bound(&swift_type!(SwiftUI.Binding), &[mangle::INT]).as_bytes(),
            Box::new(move |out| {
                let v = g.get();
                core::ptr::copy_nonoverlapping(v.to_le_bytes().as_ptr(), out, 8);
            }),
            Box::new(move |v| {
                let mut b = [0u8; 8];
                core::ptr::copy_nonoverlapping(v, b.as_mut_ptr(), 8);
                s.set(i64::from_le_bytes(b));
            }),
        )
    }
}

pub fn bool_binding(state: &BoundState<bool>) -> Vec<u8> {
    let g = state.clone();
    let s = state.clone();
    unsafe {
        binding_get_set(
            resolve::bool_meta(),
            mangle::bound(&swift_type!(SwiftUI.Binding), &[mangle::BOOL]).as_bytes(),
            Box::new(move |out| *out = u8::from(g.get())),
            Box::new(move |v| s.set(*v != 0)),
        )
    }
}

swift_cases! {
    /// `SwiftUI.HorizontalEdge`.
    pub enum HorizontalEdge {
        Leading = 0,
        Trailing = 1,
    }
}

/// Apply a modifier that takes `isPresented:` and a `@ViewBuilder` content.
///
/// The shape most of SwiftUI's presentation modifiers share: three words of
/// `Binding<Bool>`, then the content closure's two, then the type arguments —
/// nine integer registers in all, so the tail spills onto the stack.
unsafe fn presenting_modifier(
    func: *const c_void,
    oret: *const c_void,
    view: &ViewHandle,
    before: &[u64],
    state: &BoundState<bool>,
    content: &ViewHandle,
) -> ViewHandle {
    unsafe {
        let mut b = bool_binding(state);
        let w = binding_words(&mut b);
        let mut ints = before.to_vec();
        ints.extend_from_slice(&w);
        ints.extend_from_slice(&view_words(content));
        generic1_modifier_ints(
            func,
            oret,
            view,
            content.meta,
            content.wt,
            &ints,
            [0.0; 4],
        )
    }
}

/// Apply `.navigationDestination(isPresented:destination:)`.
pub fn navigation_destination(
    view: &ViewHandle,
    state: &BoundState<bool>,
    destination: &ViewHandle,
) -> ViewHandle {
    unsafe {
        presenting_modifier(
            resolve::navigation_destination_fn(),
            resolve::navigation_destination_oret(),
            view,
            &[],
            state,
            destination,
        )
    }
}

/// Apply `.inspector(isPresented:content:)`.
pub fn inspector(
    view: &ViewHandle,
    state: &BoundState<bool>,
    content: &ViewHandle,
) -> ViewHandle {
    unsafe {
        presenting_modifier(
            resolve::inspector_fn(),
            resolve::inspector_oret(),
            view,
            &[],
            state,
            content,
        )
    }
}

/// Apply `.swipeActions(edge:allowsFullSwipe:isPresented:content:)`.
///
/// `isPresented` is an *optional* binding here. A `Binding` holds a class
/// reference, so the optional is the same seventeen bytes with `nil` as a null
/// one — still three registers, not an address.
pub fn swipe_actions(
    view: &ViewHandle,
    edge: HorizontalEdge,
    allows_full_swipe: bool,
    content: &ViewHandle,
) -> ViewHandle {
    unsafe {
        let e = abi::enum_argument(abi::metadata0(resolve::horizontal_edge_ma()), edge.tag());
        let payload = abi::resolve_type_by_mangled_name(mangle::bound("7SwiftUI7BindingV", &[mangle::BOOL]).as_bytes());
        let opt = abi::resolve_type_by_mangled_name(mangle::optional(&mangle::bound("7SwiftUI7BindingV", &[mangle::BOOL])).as_bytes());
        let mut nil = vec![0u8; abi::type_bytes(opt).max(24)];
        abi::store_optional_tag(&mut nil, payload, false);
        let mut ints = vec![e, u64::from(allows_full_swipe)];
        ints.extend_from_slice(&binding_words(&mut nil));
        ints.extend_from_slice(&view_words(content));
        generic1_modifier_ints(
            resolve::swipe_actions_fn(),
            resolve::swipe_actions_oret(),
            view,
            content.meta,
            content.wt,
            &ints,
            [0.0; 4],
        )
    }
}

/// Apply `.popover(isPresented:attachmentAnchor:arrowEdges:content:)`.
///
/// The public symbol, where [`super::popover`] goes through `popoverCore` with
/// SwiftUI's own defaults. Everything is the caller's here: a binding rather
/// than a fixed `Bool`, an explicit attachment point and explicit arrow edges.
///
/// The anchor is `PopoverAttachmentAnchor.point(UnitPoint(x:y:))`. Both of that
/// enum's cases carry a payload, so the tags are the declaration order and
/// `.point` is 1; `UnitPoint` is two frozen `CGFloat`s written straight into the
/// payload, which is why this case is reachable without `Anchor<CGRect>.Source`.
pub fn popover_anchored(
    view: &ViewHandle,
    state: &BoundState<bool>,
    anchor: (f64, f64),
    arrow_edge: Edge,
    content: &ViewHandle,
) -> ViewHandle {
    unsafe {
        let mut b = bool_binding(state);
        let w = binding_words(&mut b);
        let anchor_meta = abi::metadata0(resolve::popover_anchor_ma());
        let mut buf = vec![0u8; abi::type_bytes(anchor_meta)];
        buf[..8].copy_from_slice(&anchor.0.to_le_bytes());
        buf[8..16].copy_from_slice(&anchor.1.to_le_bytes());
        abi::inject_enum_tag(&mut buf, anchor_meta, 1);
        // `Edge` is a four-case enum with no payloads, so it is one byte in a
        // register and its tag is the declaration order.
        let edge = abi::enum_argument(abi::metadata0(resolve::edge_ma()), arrow_edge as u32);
        let mut ints = w.to_vec();
        ints.push(buf.as_ptr() as u64);
        ints.push(edge);
        ints.extend_from_slice(&view_words(content));
        generic1_modifier_ints(
            resolve::popover_fn(),
            resolve::popover_oret(),
            view,
            content.meta,
            content.wt,
            &ints,
            [0.0; 4],
        )
    }
}

/// One edge of a rectangle.
///
/// Four cases, none with a payload, so the tags are the declaration order and
/// the whole value fits in a register.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Edge {
    Top = 0,
    Leading = 1,
    Bottom = 2,
    Trailing = 3,
}


/// `Toggle(isOn:) { label }` — a toggle whose label is any view.
///
/// [`toggle_bound`] takes a string, which becomes a `Text`. The convenience
/// that takes a symbol name — `Toggle(_:systemImage:isOn:)` — is inlinable and
/// exports nothing, so it cannot be called; but it only builds a `Label`, and
/// this takes any view at all. `Toggle(isOn:) { label("Wi-Fi", "wifi") }` gets
/// there and more.
pub fn toggle_labelled(label: &ViewHandle, state: &BoundState<bool>) -> ViewHandle {
    unsafe {
        let binding = bool_binding_of(state);
        let b0 = u64::from_le_bytes(binding[0..8].try_into().expect("binding 0"));
        let b1 = u64::from_le_bytes(binding[8..16].try_into().expect("binding 1"));
        let b2 = binding[16] as u64;

        // `Toggle<Label>` — one parameter, so its witness is at 1.
        let (known_meta, known_wt, known_size) = super::result_type(&super::TOGGLE_ANY_T);
        let typed = super::substitute_args(known_meta, 2, &[(0, label.meta)], &[(1, label.wt)]);
        let (meta, wt) = typed.unwrap_or((known_meta, known_wt));
        let size = if typed.is_some() { abi::type_bytes(meta) } else { known_size };
        let (label_meta, label_wt) = match typed {
            Some(_) => (label.meta, label.wt),
            None => (resolve::anyview_meta(), resolve::anyview_wt()),
        };
        let kind = if typed.is_some() { super::Content::Concrete } else { super::Content::Erased };

        // The label builder escapes: SwiftUI re-runs it whenever the toggle is
        // laid out, so the context and the child it reads are leaked.
        let ctx = Box::leak(Box::new(abi::ClosureCtx::new(
            super::emit_tuple,
            super::Child::leak(label, kind),
        ))) as *mut abi::ClosureCtx;

        let mut result = vec![0u8; size];
        // Toggle.init(isOn:label:)
        //   x0-x2 = Binding<Bool>, x3 = label fn, x4 = label ctx, x8 = @out,
        //   x20 = Self.Type.
        //
        // `Label: View` is a requirement of `Toggle` itself rather than of this
        // initialiser, so it is recoverable from `Self` and not passed — the
        // same reason the string overload next door passes only `x20`.
        core::arch::asm!("blr {f}", f = in(reg) resolve::toggle_label_init(),
            in("x0") b0, in("x1") b1, in("x2") b2,
            in("x3") abi::swift_closure_thunk as *const c_void,
            in("x4") ctx,
            in("x5") label_meta,
            in("x6") label_wt,
            in("x8") result.as_mut_ptr(),
            in("x20") meta,
            lateout("x7") _,
            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
        );
        wrap_owned(result, meta, wt)
    }
}


/// `Toggle(_:isOn: [Binding<Bool>])` — one toggle over several bindings.
///
/// The "select all" row at the top of a settings group: it reads *mixed* when
/// the sources disagree, and setting it sets every one of them. There was no
/// way to express that — a toggle took exactly one binding, so the mixed state
/// had to be faked with a third value that nothing wrote back to.
///
/// `sources` is `@in` and generic, so the array travels by address; its
/// elements are real `Binding<Bool>`s, each seventeen bytes in a
/// twenty-four-byte stride, which is why they are placed by stride rather than
/// packed.
///
/// Swift spells this `Toggle("All", sources: items, isOn: \.self)` — the
/// `isOn: [Binding<Bool>]` overload exists and is what this calls, but the
/// compiler cannot tell it from the single-binding one and rejects the array
/// even with its type written out. Both spellings reach the same initialiser;
/// the parity case uses the one that compiles.
pub fn toggle_sources(label: &str, states: &[&BoundState<bool>]) -> ViewHandle {
    assert!(!states.is_empty(), "a Toggle over sources needs at least one");
    unsafe {
        let ss = abi::swift_string(label);
        let s0 = u64::from_le_bytes(ss[..8].try_into().expect("label lo"));
        let s1 = u64::from_le_bytes(ss[8..].try_into().expect("label hi"));
        let (k0, k1, k2, k3) = localized_string_key(s0, s1);

        let element = abi::resolve_type_by_mangled_name(b"7SwiftUI7BindingVySbG");
        assert!(!element.is_null(), "Binding<Bool> metadata");
        let stride = abi::type_bytes(element);
        let mut bytes = vec![0u8; stride * states.len()];
        for (i, state) in states.iter().enumerate() {
            let b = bool_binding_of(state);
            bytes[i * stride..i * stride + b.len()].copy_from_slice(&b);
        }
        let array = abi::swift_array(element, states.len(), &bytes);

        let array_meta = abi::resolve_type_by_mangled_name(b"Say7SwiftUI7BindingVySbGG");
        assert!(!array_meta.is_null(), "Array<Binding<Bool>> metadata");
        let rac = super::conformance_wt(array_meta, resolve::random_access_proto());
        assert!(!rac.is_null(), "Array<Binding<Bool>>: RandomAccessCollection");

        let (meta, wt, size) = super::result_type(&super::TOGGLE_T);
        let mut result = vec![0u8; size];
        // Toggle.init(_:isOn:)
        //   x0-x3 = LocalizedStringKey, x4 = @in Sources — generic, so the
        //   array travels by address — x5 = Sources.Type,
        //   x6 = Sources: RandomAccessCollection WT, x8 = @out,
        //   x20 = Self.Type.
        //
        //   `Sources.Element == Binding<Bool>` is a *same-type* requirement, so
        //   it needs no witness of its own.
        core::arch::asm!("blr {f}", f = in(reg) resolve::toggle_sources_init(),
            in("x0") k0, in("x1") k1, in("x2") k2, in("x3") k3,
            in("x4") &array as *const u64,
            in("x5") array_meta,
            in("x6") rac,
            in("x8") result.as_mut_ptr(),
            in("x20") meta,
            lateout("x7") _,
            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
        );
        wrap_owned(result, meta, wt)
    }
}