1use super::*;
4use crate::abi;
5use crate::resolve;
6use core::ffi::c_void;
7use crate::mangle::swift_type;
8
9swift_cases! {
12 pub enum LabeledPairRole {
14 Label = 0,
15 Content = 1,
16 }
17}
18
19pub fn accessibility_labeled_pair(
21 view: &ViewHandle,
22 role: LabeledPairRole,
23 id: HashableValue<'_>,
24) -> ViewHandle {
25 unsafe {
26 let meta = abi::metadata0(resolve::a11y_labeled_pair_role_ma());
27 let r = abi::enum_argument(meta, role.tag());
28 let (bytes, id_meta, wt) = id.parts();
29 generic1_modifier_ints(
30 resolve::a11y_labeled_pair_fn(),
31 resolve::a11y_labeled_pair_oret(),
32 view,
33 id_meta,
34 wt,
35 &[r, bytes.as_ptr() as u64, namespace_id()],
36 [0.0; 4],
37 )
38 }
39}
40
41pub fn glass_effect_id(view: &ViewHandle, id: Option<HashableValue<'_>>) -> ViewHandle {
43 unsafe {
44 let probe = id.unwrap_or(HashableValue::Int(0));
45 let (bytes, meta, wt) = probe.parts();
46 let opt = abi::generic_metadata1(resolve::optional_ma(), meta);
47 let mut buf = vec![0u8; abi::type_bytes(opt)];
48 if id.is_some() {
49 buf[..bytes.len()].copy_from_slice(&bytes);
50 }
51 abi::store_optional_tag(&mut buf, meta, id.is_some());
52 generic1_modifier_ints(
55 resolve::glass_effect_id_fn(),
56 resolve::glass_effect_id_oret(),
57 view,
58 meta,
59 wt,
60 &[buf.as_ptr() as u64, namespace_id()],
61 [0.0; 4],
62 )
63 }
64}
65
66pub(crate) unsafe fn image_word(name: &str) -> u64 {
71 unsafe {
72 let s = abi::swift_string(name);
73 abi::call_regs(
74 resolve::image_system_init(),
75 abi::ints(&[
76 u64::from_le_bytes(s[..8].try_into().expect("lo")),
77 u64::from_le_bytes(s[8..].try_into().expect("hi")),
78 ]),
79 [0.0; 4],
80 resolve::image_meta() as u64,
81 )[0]
82 }
83}
84
85pub(crate) unsafe fn image_named_word(name: &str) -> u64 {
96 unsafe {
97 let s = abi::swift_string(name);
98 abi::call_regs(
99 resolve::image_named_init(),
100 abi::ints(&[
101 u64::from_le_bytes(s[..8].try_into().expect("lo")),
102 u64::from_le_bytes(s[8..].try_into().expect("hi")),
103 0, ]),
105 [0.0; 4],
106 resolve::image_meta() as u64,
107 )[0]
108 }
109}
110
111pub fn image_named(name: &str) -> ViewHandle {
113 unsafe {
114 let word = image_named_word(name);
115 ViewHandle::wrap(
116 &word as *const u64 as *const c_void,
117 resolve::image_meta(),
118 resolve::image_wt(),
119 )
120 }
121}
122
123pub fn image_system(name: &str) -> ViewHandle {
125 unsafe {
126 let word = image_word(name);
127 ViewHandle::wrap(
128 &word as *const u64 as *const c_void,
129 resolve::image_meta(),
130 resolve::image_wt(),
131 )
132 }
133}
134
135pub(crate) unsafe fn image_bytes(name: &str) -> Vec<u8> {
137 unsafe { image_word(name).to_le_bytes().to_vec() }
138}
139
140pub fn dialog_icon(view: &ViewHandle, system_name: Option<&str>) -> ViewHandle {
142 unsafe {
143 let word = system_name.map_or(0, |n| {
147 u64::from_le_bytes(image_bytes(n)[..8].try_into().expect("image word"))
148 });
149 opaque_modifier(
150 resolve::dialog_icon_fn(),
151 resolve::dialog_icon_oret(),
152 view,
153 &[word],
154 [0.0; 4],
155 )
156 }
157}
158
159pub fn default_app_storage_standard(view: &ViewHandle) -> ViewHandle {
161 unsafe {
162 let cls = crate::objc::class_named(c"NSUserDefaults");
165 type Send0 = unsafe extern "C" fn(*mut c_void, *const c_void) -> *mut c_void;
166 let send: Send0 = core::mem::transmute(crate::objc::msg_send_addr());
167 let defaults = send(cls, crate::objc::sel(c"standardUserDefaults"));
168 assert!(!defaults.is_null(), "UserDefaults.standard");
169 opaque_modifier(
170 resolve::default_app_storage_fn(),
171 resolve::default_app_storage_oret(),
172 view,
173 &[defaults as u64],
174 [0.0; 4],
175 )
176 }
177}
178
179pub fn default_app_storage_suite(view: &ViewHandle, suite: &str) -> Option<ViewHandle> {
190 unsafe {
191 let cls = crate::objc::class_named(c"NSUserDefaults");
192 type Alloc = unsafe extern "C" fn(*mut c_void, *const c_void) -> *mut c_void;
193 type InitSuite =
194 unsafe extern "C" fn(*mut c_void, *const c_void, *mut c_void) -> *mut c_void;
195 let send_alloc: Alloc = core::mem::transmute(crate::objc::msg_send_addr());
196 let send_init: InitSuite = core::mem::transmute(crate::objc::msg_send_addr());
197 let raw = send_alloc(cls, crate::objc::sel(c"alloc"));
198 let name = crate::objc::nsstring(suite);
199 let defaults = send_init(raw, crate::objc::sel(c"initWithSuiteName:"), name);
200 if defaults.is_null() {
201 return None;
202 }
203 Some(opaque_modifier(
204 resolve::default_app_storage_fn(),
205 resolve::default_app_storage_oret(),
206 view,
207 &[defaults as u64],
208 [0.0; 4],
209 ))
210 }
211}
212
213pub(crate) unsafe fn file_url(path: &str) -> Vec<u8> {
223 unsafe {
224 let ns_string = crate::objc::class_named(c"NSString");
225 type Send1<T> = unsafe extern "C" fn(*mut c_void, *const c_void, T) -> *mut c_void;
226 let send: Send1<*const i8> = core::mem::transmute(crate::objc::msg_send_addr());
227 let cpath = std::ffi::CString::new(path).expect("path");
228 let s = send(ns_string, crate::objc::sel(c"stringWithUTF8String:"), cpath.as_ptr());
229 let ns_url = crate::objc::class_named(c"NSURL");
230 let send_obj: Send1<*mut c_void> = core::mem::transmute(crate::objc::msg_send_addr());
231 let url = send_obj(ns_url, crate::objc::sel(c"fileURLWithPath:"), s);
232 assert!(!url.is_null(), "NSURL for {path}");
233
234 let meta = crate::type_by_name!(swift_type!(Foundation.URL).as_bytes());
235 let mut out = vec![0u8; abi::type_bytes(meta)];
236 abi::call_out(
237 resolve::url_bridge_from_objc(),
238 abi::ints(&[url as u64]),
239 [0.0; 4],
240 meta as u64,
241 out.as_mut_ptr(),
242 );
243 out
244 }
245}
246
247pub fn file_dialog_default_directory(view: &ViewHandle, path: Option<&str>) -> ViewHandle {
249 unsafe {
250 let payload = crate::type_by_name!(swift_type!(Foundation.URL).as_bytes());
251 let v = path.map(|p| file_url(p));
252 let b = opt_resilient_bytes(payload, swift_type!(opt Foundation.URL).as_bytes(), v.as_deref());
253 modifier_indirect(
254 resolve::file_dialog_default_directory_fn(),
255 resolve::file_dialog_default_directory_oret(),
256 view,
257 &b,
258 )
259 }
260}
261
262pub fn preview_device(view: &ViewHandle, name: Option<&str>) -> ViewHandle {
264 unsafe {
265 let meta = abi::metadata0(resolve::preview_device_ma());
266 let mut buf = vec![0u8; abi::type_bytes(meta)];
267 if let Some(n) = name {
268 let s = abi::swift_string(n);
269 abi::call_out(
270 resolve::preview_device_init(),
271 abi::ints(&[
272 u64::from_le_bytes(s[..8].try_into().expect("lo")),
273 u64::from_le_bytes(s[8..].try_into().expect("hi")),
274 ]),
275 [0.0; 4],
276 meta as u64,
277 buf.as_mut_ptr(),
278 );
279 }
280 let b = opt_resilient_bytes(meta, swift_type!(opt SwiftUI.PreviewDevice).as_bytes(), name.map(|_| &buf[..]));
281 modifier_indirect(resolve::preview_device_fn(), resolve::preview_device_oret(), view, &b)
282 }
283}
284
285#[cfg(test)]
286mod url_tests {
287 use super::*;
288
289 #[test]
294 fn bridged_file_url_round_trips() {
295 unsafe {
296 let meta = crate::type_by_name!(swift_type!(Foundation.URL).as_bytes());
297 let mut bytes = file_url("/tmp/rswift-parity");
298 let d = crate::diag::dump_any(bytes.as_mut_ptr().cast(), meta, 1);
299 assert!(d.contains("file:///tmp/rswift-parity"), "bridged URL was {d}");
300 }
301 }
302
303 #[test]
304 fn file_dialog_default_directory_builds_either_way() {
305 let _ = file_dialog_default_directory(&text("Fx"), Some("/tmp/rswift-parity"));
306 let _ = file_dialog_default_directory(&text("Fx"), None);
307 }
308}
309
310#[derive(Clone, Copy, Debug, PartialEq, Eq)]
316pub enum TextContentKind {
317 Username,
318 Password,
319 OneTimeCode,
320}
321
322impl TextContentKind {
323 fn constant(self) -> &'static core::ffi::CStr {
324 match self {
325 Self::Username => c"NSTextContentTypeUsername",
326 Self::Password => c"NSTextContentTypePassword",
327 Self::OneTimeCode => c"NSTextContentTypeOneTimeCode",
328 }
329 }
330}
331
332pub fn text_content_type(view: &ViewHandle, kind: Option<TextContentKind>) -> ViewHandle {
334 unsafe {
335 let word = kind.map_or(0, |k| {
338 let slot = resolve::sym(k.constant());
339 assert!(!slot.is_null(), "missing constant: {:?}", k.constant());
340 *(slot as *const u64)
341 });
342 opaque_modifier(
343 resolve::text_content_type_fn(),
344 resolve::text_content_type_oret(),
345 view,
346 &[word],
347 [0.0; 4],
348 )
349 }
350}
351
352#[derive(Clone, Copy)]
361pub struct Image(u64);
362
363#[derive(Clone, Copy, Debug, PartialEq, Eq)]
365pub enum ResizingMode {
366 Tile,
367 Stretch,
368}
369
370impl ResizingMode {
371 fn descriptor(self) -> &'static core::ffi::CStr {
373 match self {
374 Self::Tile => c"$s7SwiftUI5ImageV12ResizingModeO4tileyA2EmFWC",
375 Self::Stretch => c"$s7SwiftUI5ImageV12ResizingModeO7stretchyA2EmFWC",
376 }
377 }
378}
379
380#[derive(Clone, Copy, Debug, PartialEq, Eq)]
386pub enum Interpolation {
387 None,
389 Low,
391 Medium,
393 High,
395}
396
397impl Interpolation {
398 fn descriptor(self) -> &'static core::ffi::CStr {
400 match self {
401 Self::None => c"$s7SwiftUI5ImageV13InterpolationO4noneyA2EmFWC",
402 Self::Low => c"$s7SwiftUI5ImageV13InterpolationO3lowyA2EmFWC",
403 Self::Medium => c"$s7SwiftUI5ImageV13InterpolationO6mediumyA2EmFWC",
404 Self::High => c"$s7SwiftUI5ImageV13InterpolationO4highyA2EmFWC",
405 }
406 }
407}
408
409#[derive(Clone, Copy, Debug, PartialEq, Eq)]
411pub enum TemplateRenderingMode {
412 Template,
413 Original,
414}
415
416impl TemplateRenderingMode {
417 fn descriptor(self) -> &'static core::ffi::CStr {
418 match self {
419 Self::Template => c"$s7SwiftUI5ImageV21TemplateRenderingModeO8templateyA2EmFWC",
420 Self::Original => c"$s7SwiftUI5ImageV21TemplateRenderingModeO8originalyA2EmFWC",
421 }
422 }
423}
424
425impl Image {
426 pub fn system(name: &str) -> Self {
428 Self(unsafe { image_word(name) })
429 }
430
431 pub fn system_variable(name: &str, value: Option<f64>) -> Self {
438 unsafe {
439 let s = abi::swift_string(name);
440 let (bits, tag) = match value {
441 Some(v) => (v.to_bits(), 0u64),
442 None => (0, 1),
443 };
444 Self(
445 abi::call_regs(
446 resolve::image_sysname_variable_init(),
447 abi::ints(&[
448 u64::from_le_bytes(s[..8].try_into().expect("lo")),
449 u64::from_le_bytes(s[8..].try_into().expect("hi")),
450 bits,
451 tag,
452 ]),
453 [0.0; 4],
454 resolve::image_meta() as u64,
455 )[0],
456 )
457 }
458 }
459
460 pub fn allowed_dynamic_range_high(self) -> Self {
468 unsafe {
469 let inner = abi::metadata0(resolve::image_dynamic_range_ma());
470 assert!(!inner.is_null(), "Image.DynamicRange metadata");
471 let value = super::static_getter_indirect(
472 resolve::image_dynamic_range_high(),
473 inner,
474 abi::type_bytes(inner),
475 );
476
477 let mut opt = vec![0u8; abi::type_bytes(inner) + 8];
478 opt[..value.len()].copy_from_slice(&value);
479 let out: u64;
480 core::arch::asm!("blr {f}", f = in(reg) resolve::image_dynamic_range_fn(),
481 in("x0") opt.as_mut_ptr(),
482 in("x1") self.0,
483 lateout("x0") out,
484 lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
485 lateout("x6") _, lateout("x7") _, lateout("x8") _, lateout("x9") _,
486 lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
487 lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
488 lateout("lr") _, lateout("x20") _, lateout("x21") _, clobber_abi("C"),
489 );
490 Self(out)
491 }
492 }
493
494 pub fn named(name: &str) -> Self {
496 Self(unsafe { image_named_word(name) })
497 }
498
499 pub fn resizable(self, mode: ResizingMode) -> Self {
507 unsafe {
508 let mut buf = resilient_case(
509 RESIZING_MODE,
510 mode.descriptor(),
511 );
512 let out: u64;
513 core::arch::asm!("blr {f}", f = in(reg) resolve::image_resizable_fn(),
514 in("d0") 0.0f64, in("d1") 0.0f64, in("d2") 0.0f64, in("d3") 0.0f64,
515 in("x0") buf.as_mut_ptr(),
516 in("x1") self.0,
517 lateout("x0") out,
518 lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
519 lateout("x6") _, lateout("x7") _, lateout("x8") _, lateout("x9") _,
520 lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
521 lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
522 lateout("lr") _, lateout("x20") _, lateout("x21") _, clobber_abi("C"),
523 );
524 Self(out)
525 }
526 }
527
528 pub fn interpolation(self, mode: Interpolation) -> Self {
535 unsafe {
536 let mut buf = resilient_case(INTERPOLATION, mode.descriptor());
537 let out: u64;
538 core::arch::asm!("blr {f}", f = in(reg) resolve::image_interpolation_fn(),
539 in("x0") buf.as_mut_ptr(),
540 in("x1") self.0,
541 lateout("x0") out,
542 lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
543 lateout("x6") _, lateout("x7") _, lateout("x8") _, lateout("x9") _,
544 lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
545 lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
546 lateout("lr") _, lateout("x20") _, lateout("x21") _, clobber_abi("C"),
547 );
548 Self(out)
549 }
550 }
551
552 pub fn symbol_rendering_mode(self, mode: Option<super::SymbolRenderingMode>) -> Self {
561 unsafe {
562 let meta = abi::metadata0(resolve::symbol_rendering_mode_ma());
563 let v = mode.map(|m| super::resilient_static(meta, m.getter()));
564 let mut buf = super::opt_resilient_bytes(
565 meta,
566 swift_type!(opt SwiftUI.SymbolRenderingMode).as_bytes(),
567 v.as_deref(),
568 );
569 let out: u64;
570 core::arch::asm!("blr {f}", f = in(reg) resolve::image_symbol_rendering_mode_fn(),
571 in("x0") buf.as_mut_ptr(),
572 in("x1") self.0,
573 lateout("x0") out,
574 lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
575 lateout("x6") _, lateout("x7") _, lateout("x8") _, lateout("x9") _,
576 lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
577 lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
578 lateout("lr") _, lateout("x20") _, lateout("x21") _, clobber_abi("C"),
579 );
580 Self(out)
581 }
582 }
583
584 pub fn antialiased(self, on: bool) -> Self {
586 unsafe {
587 let out: u64;
588 core::arch::asm!("blr {f}", f = in(reg) resolve::image_antialiased_fn(),
589 in("x0") u64::from(on),
590 in("x1") self.0,
591 lateout("x0") out,
592 lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
593 lateout("x6") _, lateout("x7") _, lateout("x8") _, lateout("x9") _,
594 lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
595 lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
596 lateout("lr") _, lateout("x20") _, lateout("x21") _, clobber_abi("C"),
597 );
598 Self(out)
599 }
600 }
601
602 pub fn rendering_mode(self, mode: Option<TemplateRenderingMode>) -> Self {
612 unsafe {
613 let mangled = TEMPLATE_RENDERING_MODE;
614 let mut buf = match mode {
615 Some(m) => resilient_case(mangled, m.descriptor()),
616 None => {
617 let mut opt = mangled.to_vec();
620 opt.extend_from_slice(b"Sg");
621 let meta = abi::resolve_type_by_mangled_name(&opt);
622 assert!(!meta.is_null(), "Optional<TemplateRenderingMode>");
623 let mut buf = vec![0u8; abi::type_bytes(meta).max(1)];
624 abi::inject_enum_tag(&mut buf, meta, 1);
625 buf
626 }
627 };
628 let out: u64;
629 core::arch::asm!("blr {f}", f = in(reg) resolve::image_rendering_mode_fn(),
630 in("x0") buf.as_mut_ptr(),
631 in("x1") self.0,
632 lateout("x0") out,
633 lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
634 lateout("x6") _, lateout("x7") _, lateout("x8") _, lateout("x9") _,
635 lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
636 lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
637 lateout("lr") _, lateout("x20") _, lateout("x21") _, clobber_abi("C"),
638 );
639 Self(out)
640 }
641 }
642
643 pub fn view(self) -> ViewHandle {
645 unsafe {
646 ViewHandle::wrap(
647 &self.0 as *const u64 as *const c_void,
648 resolve::image_meta(),
649 resolve::image_wt(),
650 )
651 }
652 }
653}
654
655const RESIZING_MODE: &[u8] = b"7SwiftUI5ImageV12ResizingModeO";
661const INTERPOLATION: &[u8] = b"7SwiftUI5ImageV13InterpolationO";
662const TEMPLATE_RENDERING_MODE: &[u8] = b"7SwiftUI5ImageV21TemplateRenderingModeO";
663
664unsafe fn resilient_case(mangled: &[u8], case_descriptor: &'static core::ffi::CStr) -> Vec<u8> {
671 unsafe {
672 let meta = abi::resolve_type_by_mangled_name(mangled);
673 assert!(!meta.is_null(), "{}", crate::diag::demangle_type(mangled));
674 let tag = resolve::sym(case_descriptor) as *const u32;
675 assert!(!tag.is_null(), "enum case descriptor: {case_descriptor:?}");
676 let mut buf = vec![0u8; abi::type_bytes(meta).max(1)];
677 abi::inject_enum_tag(&mut buf, meta, tag.read());
678 buf
679 }
680}
681
682pub fn async_image(spec: Option<&str>, scale: f64) -> ViewHandle {
688 unsafe {
689 let ty = crate::mangle::ty("AsyncImage").of(vec![crate::mangle::ty("Image")]).mangle();
690 let meta = abi::resolve_type_by_mangled_name(ty.as_bytes());
691 assert!(!meta.is_null(), "AsyncImage<Image> metadata");
692 let mut url = super::optional_url(spec);
693 let mut value = vec![0u8; abi::type_bytes(meta)];
694 core::arch::asm!("blr {f}", f = in(reg) resolve::async_image_init(),
695 in("x0") url.as_mut_ptr(),
696 in("d0") scale,
697 in("x8") value.as_mut_ptr(),
698 in("x20") meta,
699 lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
700 lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x9") _,
701 lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
702 lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
703 lateout("lr") _, lateout("x21") _, clobber_abi("C"),
704 );
705 let wt = abi::get_view_wt(meta);
706 assert!(!wt.is_null(), "AsyncImage !: View");
707 wrap_buf(&value, meta, wt)
708 }
709}
710
711pub fn share_link_url(spec: &str) -> ViewHandle {
720 unsafe {
721 let ty = crate::mangle::ty("ShareLink")
722 .of(vec![
723 crate::mangle::Type::Verbatim("s15CollectionOfOneVy10Foundation3URLVG".into()),
724 crate::mangle::Type::Verbatim("s5NeverO".into()),
725 crate::mangle::Type::Verbatim("s5NeverO".into()),
726 crate::mangle::ty("DefaultShareLinkLabel"),
727 ])
728 .mangle();
729 let meta = abi::resolve_type_by_mangled_name(ty.as_bytes());
730 assert!(!meta.is_null(), "ShareLink metadata: {ty}");
731
732 let mut url = super::optional_url(Some(spec));
733 let mut value = vec![0u8; abi::type_bytes(meta)];
734 core::arch::asm!(
735 "str xzr, [sp, #-16]!",
736 "blr {f}",
737 "add sp, sp, #16",
738 f = in(reg) resolve::share_link_url_init(),
739 in("x0") url.as_mut_ptr(),
740 in("x1") 0u64, in("x2") 0u64, in("x3") 0u64, in("x4") 0u64,
741 in("x5") 0u64, in("x6") 0u64, in("x7") 0u64,
742 in("x8") value.as_mut_ptr(),
743 lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
744 lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
745 lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
746 clobber_abi("C"),
747 );
748 let wt = abi::get_view_wt(meta);
749 assert!(!wt.is_null(), "ShareLink !: View");
750 wrap_buf(&value, meta, wt)
751 }
752}