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
//! Button.
//!
//! * Scroll
//! * Window

use super::*;
use crate::abi;
use crate::resolve;
use core::ffi::c_void;
use crate::mangle;
use crate::mangle::swift_type;

// ── Button ──

mangle::result_types! {
    /// `SwiftUI.Button<SwiftUI.Text>`
    /// `Button<Text>`
    pub(crate) BUTTON_T = ty("Button").of(vec![ty("Text")]);
    /// `Button<AnyView>`
    /// `Button<AnyView>`
    pub(crate) BUTTON_LABEL_T = ty("Button").of(vec![ty("AnyView")]);
}

/// Trampoline for a Button action: a void closure ignores the `@out` slot.
pub(crate) unsafe extern "C" fn invoke_action(_out: *mut u8, payload: *mut c_void) {
    let f = unsafe { boxed_ref::<dyn Fn()>(payload) };
    f();
}

swift_statics! {
    /// `SwiftUI.ButtonRole` — what a button *is*, not how it looks.
    ///
    /// The style system reads it: a destructive button is red wherever red
    /// means destructive, and a cancel button is the one a sheet's dismiss
    /// gesture triggers. Setting a colour by hand gets the first and none of
    /// the rest.
    pub enum ButtonRole {
        Destructive = c"$s7SwiftUI10ButtonRoleV11destructiveACvgZ",
        Cancel = c"$s7SwiftUI10ButtonRoleV6cancelACvgZ",
        Confirm = c"$s7SwiftUI10ButtonRoleV7confirmACvgZ",
        Close = c"$s7SwiftUI10ButtonRoleV5closeACvgZ",
    }
}

/// `Button(_:role:action:)`.
///
/// `None` is the plain button [`button`] builds; the roles are what Apple's
/// `Button("Delete", role: .destructive)` is about.
pub fn button_role(
    label: &str,
    role: Option<ButtonRole>,
    callback: impl Fn() + 'static,
) -> ViewHandle {
    unsafe {
        let ss = abi::swift_string(label);
        let s0 = u64::from_le_bytes(ss[..8].try_into().expect("lo"));
        let s1 = u64::from_le_bytes(ss[8..].try_into().expect("hi"));
        let (k0, k1, k2, k3) = localized_string_key(s0, s1);

        let boxed: Box<dyn Fn()> = Box::new(callback);
        let payload = Box::into_raw(Box::new(boxed)) as *mut c_void;
        let ctx = Box::leak(Box::new(abi::ClosureCtx::new(run_boxed_action, payload)))
            as *mut abi::ClosureCtx;

        // `ButtonRole` is resilient, so the optional travels by address.
        let role_meta = abi::metadata0(resolve::button_role_ma());
        assert!(!role_meta.is_null(), "ButtonRole metadata");
        let value = role.map(|r| {
            super::static_getter_indirect(r.resolved(), role_meta, abi::type_bytes(role_meta))
        });
        let mut arg = super::opt_resilient_bytes(
            role_meta,
            swift_type!(opt SwiftUI.ButtonRole).as_bytes(),
            value.as_deref(),
        );

        let (meta, wt, size) = result_type(&BUTTON_T);
        let mut result = vec![0u8; size];
        // `Button<Text>.init(_:role:action:)`
        //   x0-x3 = LocalizedStringKey, x4 = @in ButtonRole?,
        //   x5 = action fn, x6 = action ctx, x8 = @out
        core::arch::asm!("blr {f}", f = in(reg) resolve::button_role_init(),
            in("x0") k0, in("x1") k1, in("x2") k2, in("x3") k3,
            in("x4") arg.as_mut_ptr(),
            in("x5") abi::swift_closure_thunk as *const c_void,
            in("x6") ctx,
            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)
    }
}

/// Create a `Button` with a text label and an action.
///
/// The action may capture. It was `fn()` — a bare pointer stored as the context
/// — which meant a button could not know anything about where it was: a row's
/// checkmark could not be told which row it belonged to, and every list had to
/// route through a global. `button_labelled` next door already boxed its
/// closure; this now does the same.
pub fn button(label: &str, callback: impl Fn() + 'static) -> 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);

        // SwiftUI keeps the action for as long as the button exists, so the
        // context outlives this call.
        let boxed: Box<dyn Fn()> = Box::new(callback);
        let payload = Box::into_raw(Box::new(boxed)) as *mut c_void;
        let ctx = Box::leak(Box::new(abi::ClosureCtx::new(run_boxed_action, payload)))
            as *mut abi::ClosureCtx;

        let (meta, wt, size) = result_type(&BUTTON_T);
        let mut result = vec![0u8; size];
        // Button<Text>.init(_:action:)
        //   x0-x3 = LocalizedStringKey, x4 = action fn, x5 = action ctx, x8 = @out
        core::arch::asm!("blr {f}", f = in(reg) resolve::button_init(),
            in("x0") k0, in("x1") k1, in("x2") k2, in("x3") k3,
            in("x4") abi::swift_closure_thunk as *const c_void,
            in("x5") ctx,
            in("x8") result.as_mut_ptr(),
            in("x20") meta,
            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("x21") _,
            clobber_abi("C"),
        );
        wrap_owned(result, meta, wt)
    }
}

/// Create a `Button` with raw C callback.
pub fn button_raw(
    label: &str,
    _callback: unsafe extern "C" fn(*mut c_void),
    _userdata: *mut c_void,
) -> ViewHandle {
    text(label)
}

// ── Scroll ──

/// Wrap in a vertical `ScrollView` with indicators shown.
pub fn scroll(view: &ViewHandle) -> ViewHandle {
    unsafe {
        // The scroll view's content is the view itself, kept below as a copy
        // the closure can hand back each time it is asked.
        let content_meta = view.meta;
        let content_wt = view.wt;
        let sv_meta = abi::generic_metadata2(resolve::scrollview_ma(), content_meta, content_wt);
        assert!(!sv_meta.is_null(), "ScrollView metadata");
        let sv_wt = abi::get_view_wt(sv_meta);
        assert!(!sv_wt.is_null(), "ScrollView !: View");

        let child = view.clone();
        let kid_handles = Child::new(&child, Content::Concrete);
        let handles = kid_handles.element();
        let payload = TuplePayload { children: handles.as_ptr(), count: 1 };
        let mut ctx = abi::ClosureCtx::new(emit_tuple, &payload as *const TuplePayload as *mut c_void);

        let mut result = vec![0u8; abi::type_bytes(sv_meta)];
        // ScrollView.init(_:showsIndicators:content:)
        //   x0 = Axis.Set (vertical == 2), x1 = showsIndicators,
        //   x2 = closure fn, x3 = closure ctx,
        //   x4 = Content.Type, x5 = Content: View WT, x8 = @out
        core::arch::asm!("blr {f}", f = in(reg) resolve::scrollview_init(),
            in("x0") 2u64,
            in("x1") 1u64,
            in("x2") abi::swift_closure_thunk as *const c_void,
            in("x3") &mut ctx as *mut abi::ClosureCtx,
            in("x4") content_meta,
            in("x5") content_wt,
            in("x8") result.as_mut_ptr(),
            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"),
        );
        wrap_owned(result, sv_meta, sv_wt)
    }
}

// ── Window ──

/// Show a view in a native window and run the event loop.
///
/// On the Mac this is `NSWindow` + `NSApp.run()`. On iOS it is the UIKit entry
/// point, which is behind the `uikit-entry` feature: `App.main()` needs none of
/// it, and declaring `UIApplicationMain` is what puts UIKit in the binary.
#[cfg(any(target_os = "macos", feature = "uikit-entry"))]
pub fn show_window(view: &ViewHandle, title: &str, width: f64, height: f64) {
    crate::window::show_window(view, title, width, height);
}

/// Which `ProgressViewStyle` to apply.
///
/// Each case is a distinct Swift type with its own conformance, which is why
/// the modifier is generic in Swift and takes a value here.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ProgressStyle {
    /// `.automatic` — `DefaultProgressViewStyle`.
    Automatic,
    /// `.circular` — a spinner.
    Circular,
    /// `.linear` — a bar.
    Linear,
}

impl ProgressStyle {
    /// The style's metadata accessor, initialiser and `ProgressViewStyle`
    /// witness.
    fn parts(self) -> (*const c_void, *const c_void, *const c_void) {
        match self {
            Self::Automatic => (
                resolve::default_progress_ma(),
                resolve::default_progress_init(),
                resolve::default_progress_wt(),
            ),
            Self::Circular => (
                resolve::circular_progress_ma(),
                resolve::circular_progress_init(),
                resolve::circular_progress_wt(),
            ),
            Self::Linear => (
                resolve::linear_progress_ma(),
                resolve::linear_progress_init(),
                resolve::linear_progress_wt(),
            ),
        }
    }
}

/// Apply `.progressViewStyle(_:)`.
///
/// Not missing after all. Searching the export table for `progressViewStyle`
/// finds nothing because the mangling compresses `ViewStyle` to
/// `08progressC5Style` — `C` substitutes the `View` of the enclosing
/// `SwiftUI.View` — so the spelled-out name never appears. The SIL for one line
/// of Swift names it outright.
///
/// `.linear` and `.automatic` are `@_alwaysEmitIntoClient` statics with no
/// symbol of their own; the structs they return have plain initialisers, which
/// is what this needs.
pub fn progress_view_style(view: &ViewHandle, style: ProgressStyle) -> ViewHandle {
    unsafe {
        let (ma, init, wt) = style.parts();
        let style_meta = abi::metadata0(ma);
        assert!(!style_meta.is_null(), "ProgressViewStyle metadata");
        let mut style_buf = vec![0u8; abi::type_bytes(style_meta)];
        core::arch::asm!("blr {f}", f = in(reg) init,
            in("x8") style_buf.as_mut_ptr(),
            in("x20") style_meta,
            lateout("x0") _, lateout("x1") _, lateout("x2") _, lateout("x3") _,
            lateout("x4") _, 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("x21") _, clobber_abi("C"),
        );

        let subst = [view.meta, style_meta, view.wt, wt];
        let (meta, rwt, size) =
            super::opaque_result_type(resolve::progress_view_style_oret(), &subst);

        let mut result = vec![0u8; size];
        let this = view.value_ptr();
        // progressViewStyle<S: ProgressViewStyle>(_:)
        //   x0 = @in_guaranteed style, then the four generic words, x20 = self.
        core::arch::asm!("blr {f}", f = in(reg) resolve::progress_view_style_fn(),
            in("x0") style_buf.as_mut_ptr(),
            in("x1") subst[0], in("x2") subst[1], in("x3") subst[2], in("x4") subst[3],
            in("x8") result.as_mut_ptr(),
            in("x20") this,
            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("x21") _, clobber_abi("C"),
        );
        wrap_owned(result, meta, rwt)
    }
}

/// Apply `.progressViewStyle(.circular)`.
pub fn progress_view_style_circular(view: &ViewHandle) -> ViewHandle {
    progress_view_style(view, ProgressStyle::Circular)
}

/// Apply `.buttonStyle(.glass(.clear))`.
///
/// `.glass(_:)` is a static on a `PrimitiveButtonStyle` extension constrained
/// to `GlassButtonStyle` — the shape that has been inlinable every other time
/// in this file. This one is exported, which is only knowable by looking.
pub fn button_style_glass_clear(view: &ViewHandle) -> ViewHandle {
    unsafe {
        let glass_meta = abi::metadata0(resolve::glass_ma());
        assert!(!glass_meta.is_null(), "Glass metadata");
        let mut glass = super::static_getter_indirect(
            resolve::glass_clear(),
            glass_meta,
            abi::type_bytes(glass_meta),
        );

        let style_meta = abi::metadata0(resolve::glass_button_style_ma());
        assert!(!style_meta.is_null(), "GlassButtonStyle metadata");
        let mut style = vec![0u8; abi::type_bytes(style_meta)];
        core::arch::asm!("blr {f}", f = in(reg) resolve::glass_button_style_of(),
            in("x0") glass.as_mut_ptr(),
            in("x8") style.as_mut_ptr(),
            in("x20") style_meta,
            lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
            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("x21") _, clobber_abi("C"),
        );

        let style_wt = resolve::glass_button_style_wt();
        let subst = [view.meta, style_meta, view.wt, style_wt];
        let (meta, wt, size) = super::opaque_result_type(resolve::button_style_oret(), &subst);
        let mut result = vec![0u8; size];
        let this = view.value_ptr();
        core::arch::asm!("blr {f}", f = in(reg) resolve::button_style_fn(),
            in("x0") style.as_mut_ptr(),
            in("x1") subst[0], in("x2") subst[1], in("x3") subst[2], in("x4") subst[3],
            in("x8") result.as_mut_ptr(),
            in("x20") this,
            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("x21") _, clobber_abi("C"),
        );
        wrap_owned(result, meta, wt)
    }
}

/// `Button(action:label:)` — a button whose label is a view rather than a string.
///
/// The action is an escaping `() -> Void`, so its context is leaked; the label
/// closure is `@noescape` and resolved during the call, so that context can live
/// on this frame.
pub fn button_labelled(label: &ViewHandle, action: impl Fn() + 'static) -> ViewHandle {
    unsafe {
        let boxed: Box<dyn Fn()> = Box::new(action);
        let payload = Box::into_raw(Box::new(boxed)) as *mut c_void;
        let act = Box::leak(Box::new(abi::ClosureCtx::new(run_boxed_action, payload)))
            as *mut abi::ClosureCtx;

        let (known_meta, known_wt, known_size) = result_type(&BUTTON_LABEL_T);
        // `Button<Label>`: one parameter, then its `View` conformance.
        let typed = super::substitute_content(known_meta, 2, 0, 1, label.meta, 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 };

        let kid_handles = super::Child::new(label, kind);
        let handles = kid_handles.element();
        let tuple = super::TuplePayload { children: handles.as_ptr(), count: 1 };
        let mut ctx = abi::ClosureCtx::new(
            super::emit_tuple,
            &tuple as *const super::TuplePayload as *mut c_void,
        );

        let mut result = vec![0u8; size];
        // Button.init(action:label:)
        //   x0 = action fn, x1 = action ctx, x2 = label fn, x3 = label ctx,
        //   x4 = Label.Type, x5 = Label: View WT, x8 = @out
        core::arch::asm!("blr {f}", f = in(reg) resolve::button_label_init(),
            in("x0") abi::swift_closure_thunk as *const c_void,
            in("x1") act,
            in("x2") abi::swift_closure_thunk as *const c_void,
            in("x3") &mut ctx as *mut abi::ClosureCtx,
            in("x4") label_meta,
            in("x5") label_wt,
            in("x8") result.as_mut_ptr(),
            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"),
        );
        wrap_owned(result, meta, wt)
    }
}

/// The `() -> Void` a `Button`'s action is.
/// Call a boxed `Fn()` held as a closure context.
///
/// The counterpart of [`invoke_action`], which transmutes the context back to a
/// bare `fn()` — which is exactly what a capturing closure cannot be.
pub(crate) unsafe extern "C" fn run_boxed_action(_out: *mut u8, payload: *mut c_void) {
    unsafe { (*(payload as *const Box<dyn Fn()>))() };
}