1use super::*;
8use super::closures::*;
9use super::dragdrop::*;
10use crate::abi;
11use crate::resolve;
12use core::ffi::c_void;
13use crate::mangle::{self, swift_type};
14
15pub fn search_selection(view: &ViewHandle, changed: impl Fn() + 'static) -> ViewHandle {
22 unsafe {
23 let value_meta = crate::type_by_name!(swift_type!(opt SwiftUI.TextSelection).as_bytes());
24 let payload = crate::type_by_name!(swift_type!(SwiftUI.TextSelection).as_bytes());
25 let width = abi::type_bytes(value_meta);
26 let mut none = vec![0u8; width];
29 abi::store_optional_tag(&mut none, payload, false);
30 let binding = binding_get_set(
31 value_meta,
32 mangle::ty("Binding")
33 .of(vec![mangle::opt(mangle::ty("TextSelection"))])
34 .mangle()
35 .as_bytes(),
36 Box::new(move |out| {
37 core::ptr::copy_nonoverlapping(none.as_ptr(), out, width);
38 }),
39 Box::new(move |_| changed()),
40 );
41 modifier_indirect(
42 resolve::search_selection_fn(),
43 resolve::search_selection_oret(),
44 view,
45 &binding,
46 )
47 }
48}
49
50#[derive(Clone, Copy, Debug, PartialEq, Eq)]
52pub enum PresentationDetent {
53 Medium,
54 Large,
55}
56
57impl PresentationDetent {
58 fn getter(self) -> &'static core::ffi::CStr {
59 match self {
60 PresentationDetent::Medium => c"$s7SwiftUI18PresentationDetentV6mediumACvgZ",
61 PresentationDetent::Large => c"$s7SwiftUI18PresentationDetentV5largeACvgZ",
62 }
63 }
64}
65
66pub(crate) unsafe fn string_set(items: &[&str]) -> u64 {
68 unsafe {
69 let meta = resolve::string_meta();
70 let stride = abi::type_bytes(meta);
71 let mut bytes = vec![0u8; stride * items.len()];
72 for (i, s) in items.iter().enumerate() {
73 bytes[i * stride..i * stride + 16].copy_from_slice(&abi::swift_string(s));
74 }
75 abi::swift_set(meta, resolve::string_hashable_wt(), items.len(), &bytes)
76 }
77}
78
79pub fn handles_external_events(
83 view: &ViewHandle,
84 preferring: &[&str],
85 allowing: &[&str],
86) -> ViewHandle {
87 unsafe {
88 opaque_modifier(
89 resolve::handles_external_events_fn(),
90 resolve::handles_external_events_oret(),
91 view,
92 &[string_set(preferring), string_set(allowing)],
93 [0.0; 4],
94 )
95 }
96}
97
98pub fn presentation_detents(view: &ViewHandle, detents: &[PresentationDetent]) -> ViewHandle {
104 unsafe {
105 let meta = abi::metadata0(resolve::presentation_detent_ma());
106 let wt = abi::get_view_wt_for_proto(meta, resolve::hashable_proto());
107 assert!(!wt.is_null(), "PresentationDetent: Hashable");
108 let stride = abi::type_bytes(meta);
109 let mut bytes = vec![0u8; stride * detents.len()];
110 for (i, d) in detents.iter().enumerate() {
111 let v = resilient_static(meta, d.getter());
112 bytes[i * stride..i * stride + v.len()].copy_from_slice(&v);
113 }
114 let set = abi::swift_set(meta, wt, detents.len(), &bytes);
115 opaque_modifier(
116 resolve::presentation_detents_fn(),
117 resolve::presentation_detents_oret(),
118 view,
119 &[set],
120 [0.0; 4],
121 )
122 }
123}
124
125pub fn searchable(
132 view: &ViewHandle,
133 text: &BoundState<String>,
134 presented: &BoundState<bool>,
135 placement: SearchFieldPlacement,
136 prompt: &str,
137) -> ViewHandle {
138 unsafe {
139 let t = text_binding(text);
140 let p = bool_binding(presented);
141 let meta = abi::metadata0(resolve::search_field_placement_ma());
145 let place = resilient_static(meta, placement.getter());
146 let (k0, k1, k2, k3) = lsk_words(prompt);
147 let mut ints: Vec<u64> = words(&t);
148 ints.extend(words(&p));
149 ints.push(place.as_ptr() as u64);
150 ints.extend_from_slice(&[k0, k1, k2, k3]);
151 let subst = [view.meta, view.wt];
152 let (m, wt, size) = opaque_result_type(resolve::searchable_oret(), &subst);
153 let r = abi::call_modifier(resolve::searchable_fn(), view.value_ptr(), view.meta, view.wt, &ints, [0.0; 4], size);
154 wrap_owned(r, m, wt)
155 }
156}
157
158pub fn search_suggestions(
160 view: &ViewHandle,
161 visibility: Visibility,
162 placements: SearchSuggestionsPlacementSet,
163) -> ViewHandle {
164 unsafe {
165 let vm = abi::metadata0(resolve::visibility_ma());
170 let sm = abi::metadata0(resolve::search_suggestions_placement_set_ma());
171 let p = resilient_static(sm, placements.getter());
172 opaque_modifier(
173 resolve::search_suggestions_fn(),
174 resolve::search_suggestions_oret(),
175 view,
176 &[abi::enum_argument(vm, visibility as u32), p.as_ptr() as u64],
177 [0.0; 4],
178 )
179 }
180}
181
182pub fn text_input_formatting_control_visibility(
184 view: &ViewHandle,
185 visibility: Visibility,
186 placements: TextInputFormattingControlPlacementSet,
187) -> ViewHandle {
188 unsafe {
189 let vm = abi::metadata0(resolve::visibility_ma());
191 let sm = abi::metadata0(resolve::text_input_formatting_set_ma());
192 let p = resilient_static(sm, placements.getter());
193 opaque_modifier(
194 resolve::text_input_formatting_fn(),
195 resolve::text_input_formatting_oret(),
196 view,
197 &[abi::enum_argument(vm, visibility as u32), p.as_ptr() as u64],
198 [0.0; 4],
199 )
200 }
201}
202
203pub fn dismissal_confirmation_dialog(
205 view: &ViewHandle,
206 title: &str,
207 should_present: bool,
208 actions: &ViewHandle,
209 message: &ViewHandle,
210) -> ViewHandle {
211 unsafe {
212 let (k0, k1, k2, k3) = lsk_words(title);
213 two_view_modifier(
214 resolve::dismissal_dialog_fn(),
215 resolve::dismissal_dialog_oret(),
216 view,
217 &[k0, k1, k2, k3, u64::from(should_present)],
218 actions,
219 message,
220 )
221 }
222}
223
224#[derive(Clone, Debug, PartialEq, Eq)]
226pub enum FileOutcome {
227 Picked(String),
229 Failed,
231}
232
233pub(crate) unsafe extern "C" fn invoke_file_result(
241 a0: u64,
242 _a1: u64,
243 _a2: u64,
244 payload: *mut c_void,
245) {
246 unsafe {
247 let f = boxed_ref::<dyn Fn(FileOutcome)>(payload);
248 let meta = abi::resolve_type_by_mangled_name(mangle::bound(&swift_type!(stdlib enum Result), &[&swift_type!(Foundation.URL), &mangle::existential("s5Error")]).as_bytes());
249 if meta.is_null() {
250 f(FileOutcome::Failed);
251 return;
252 }
253 if abi::read_enum_tag(a0 as *const u8, meta) != 0 {
254 f(FileOutcome::Failed);
255 return;
256 }
257 let (s0, s1): (u64, u64);
260 core::arch::asm!("blr {f}", f = in(reg) resolve::url_absolute_string(),
261 in("x20") a0,
262 lateout("x0") s0, lateout("x1") s1,
263 lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
264 lateout("x6") _, lateout("x7") _, lateout("x8") _, lateout("x9") _,
265 lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
266 lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
267 lateout("lr") _, lateout("x21") _, clobber_abi("C"),
268 );
269 let mut raw = [0u8; 16];
270 raw[..8].copy_from_slice(&s0.to_le_bytes());
271 raw[8..].copy_from_slice(&s1.to_le_bytes());
272 f(FileOutcome::Picked(abi::read_swift_string(raw)));
273 }
274}
275
276pub fn file_importer(
278 view: &ViewHandle,
279 state: &BoundState<bool>,
280 kinds: &[ContentType],
281 completion: impl Fn(FileOutcome) + 'static,
282) -> ViewHandle {
283 unsafe {
284 let mut b = bool_binding(state);
285 let w = binding_words(&mut b);
286 let c = wide_words(invoke_file_result, boxed_ctx(Box::new(completion) as Box<dyn Fn(FileOutcome)>));
287 opaque_modifier(
288 resolve::file_importer_fn(),
289 resolve::file_importer_oret(),
290 view,
291 &[w[0], w[1], w[2], content_types(kinds), c[0], c[1]],
292 [0.0; 4],
293 )
294 }
295}
296
297pub fn search_scopes(
303 view: &ViewHandle,
304 scope: &BoundState<i64>,
305 scopes: &ViewHandle,
306) -> ViewHandle {
307 unsafe {
308 let b = i64_binding(scope);
313 let av = scopes.meta;
316 let aw = scopes.wt;
317 let im = resolve::int_meta();
318 let ih = resolve::int_hashable_wt();
319 let subst = [view.meta, im, av, view.wt, ih, aw];
320 let (meta, wt, size) = opaque_result_type(resolve::search_scopes_oret(), &subst);
321 let mut ints = vec![b.as_ptr() as u64];
322 ints.extend_from_slice(&view_words(scopes));
323 ints.extend_from_slice(&[view.meta as u64, im as u64, av as u64]);
324 ints.extend_from_slice(&[view.wt as u64, ih as u64, aw as u64]);
325 let r = abi::call_modifier_generic(resolve::search_scopes_fn(), view.value_ptr(), &ints, [0.0; 4], size);
326 wrap_owned(r, meta, wt)
327 }
328}
329
330pub(crate) unsafe fn optional_url(spec: Option<&str>) -> Vec<u8> {
337 unsafe {
338 let payload = abi::metadata0(resolve::url_ma());
339 let opt = crate::type_by_name!(swift_type!(opt Foundation.URL).as_bytes());
340 assert!(!opt.is_null(), "Optional<URL>");
341 let mut buf = vec![0u8; abi::type_bytes(opt)];
342 match spec {
343 Some(s) => {
344 let ss = abi::swift_string(s);
345 let s0 = u64::from_le_bytes(ss[..8].try_into().expect("url lo"));
346 let s1 = u64::from_le_bytes(ss[8..].try_into().expect("url hi"));
347 core::arch::asm!("blr {f}", f = in(reg) resolve::url_string_init(),
348 in("x0") s0, in("x1") s1,
349 in("x8") buf.as_mut_ptr(), in("x20") payload,
350 lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
351 lateout("x6") _, lateout("x7") _, lateout("x9") _, lateout("x10") _,
352 lateout("x11") _, lateout("x12") _, lateout("x13") _, lateout("x14") _,
353 lateout("x15") _, lateout("x16") _, lateout("x17") _, lateout("lr") _,
354 lateout("x21") _, clobber_abi("C"),
355 );
356 }
357 None => abi::store_optional_tag(&mut buf, payload, false),
358 }
359 buf
360 }
361}
362
363pub fn file_mover(
365 view: &ViewHandle,
366 state: &BoundState<bool>,
367 file: Option<&str>,
368 completion: impl Fn(FileOutcome) + 'static,
369) -> ViewHandle {
370 unsafe {
371 let mut b = bool_binding(state);
372 let w = binding_words(&mut b);
373 let url = optional_url(file);
374 let c = wide_words(invoke_file_result, boxed_ctx(Box::new(completion) as Box<dyn Fn(FileOutcome)>));
375 opaque_modifier(
376 resolve::file_mover_fn(),
377 resolve::file_mover_oret(),
378 view,
379 &[w[0], w[1], w[2], url.as_ptr() as u64, c[0], c[1]],
380 [0.0; 4],
381 )
382 }
383}
384
385pub fn file_exporter(
392 view: &ViewHandle,
393 state: &BoundState<bool>,
394 item: Option<&str>,
395 kinds: &[ContentType],
396 default_filename: Option<&str>,
397 completion: impl Fn(FileOutcome) + 'static,
398 cancellation: impl Fn() + 'static,
399) -> ViewHandle {
400 unsafe {
401 let mut b = bool_binding(state);
402 let w = binding_words(&mut b);
403 let meta = resolve::string_meta();
404 let opt = abi::resolve_type_by_mangled_name(mangle::optional(mangle::STRING).as_bytes());
405 let mut boxed = vec![0u8; abi::type_bytes(opt)];
410 if let Some(s) = item {
411 boxed[..16].copy_from_slice(&abi::swift_string(s));
412 }
413 abi::store_optional_tag(&mut boxed, meta, item.is_some());
414 let name = optional_words(
415 meta,
416 opt,
417 &default_filename.map(|s| abi::swift_string(s)).unwrap_or([0u8; 16]),
418 default_filename.is_some(),
419 );
420 let done = wide_words(invoke_file_result, boxed_ctx(Box::new(completion) as Box<dyn Fn(FileOutcome)>));
421 let cancel = action_words(cancellation);
422 let mut ints = w.to_vec();
423 ints.push(boxed.as_ptr() as u64);
424 ints.push(content_types(kinds));
425 ints.extend_from_slice(&name);
426 ints.extend_from_slice(&done);
427 ints.extend_from_slice(&cancel);
428 generic1_modifier_ints(
429 resolve::file_exporter_fn(),
430 resolve::file_exporter_oret(),
431 view,
432 meta,
433 {
434 let wt = abi::get_view_wt_for_proto(meta, resolve::transferable_proto());
437 assert!(!wt.is_null(), "String: Transferable");
438 wt
439 },
440 &ints,
441 [0.0; 4],
442 )
443 }
444}
445
446pub fn navigation_document_url(view: &ViewHandle, spec: &str) -> ViewHandle {
452 unsafe {
453 let url = optional_url(Some(spec));
454 opaque_modifier(
455 resolve::navigation_document_url_fn(),
456 resolve::navigation_document_url_oret(),
457 view,
458 &[url.as_ptr() as u64],
459 [0.0; 4],
460 )
461 }
462}
463
464pub fn navigation_document(view: &ViewHandle, item: &str) -> ViewHandle {
469 unsafe {
470 let meta = resolve::string_meta();
471 let wt = abi::get_view_wt_for_proto(meta, resolve::transferable_proto());
472 assert!(!wt.is_null(), "String: Transferable");
473 let s = abi::swift_string(item);
474 generic1_modifier_ints(
475 resolve::navigation_document_item_fn(),
476 resolve::navigation_document_item_oret(),
477 view,
478 meta,
479 wt,
480 &[s.as_ptr() as u64],
481 [0.0; 4],
482 )
483 }
484}
485
486pub fn file_dialog_url_enabled(view: &ViewHandle, enabled: bool) -> ViewHandle {
497 unsafe {
498 let meta = abi::resolve_type_by_mangled_name(
499 mangle::pack(
500 mangle::Type::Nominal {
501 module: "Foundation",
502 path: vec!["Predicate"],
503 kind: mangle::Kind::Struct,
504 },
505 vec![mangle::Type::Nominal {
506 module: "Foundation",
507 path: vec!["URL"],
508 kind: mangle::Kind::Struct,
509 }],
510 )
511 .mangle()
512 .as_bytes(),
513 );
514 assert!(!meta.is_null(), "Predicate<Pack{{URL}}> metadata");
515 let getter = if enabled { resolve::predicate_true() } else { resolve::predicate_false() };
516 let value = abi::static_out(getter, meta, abi::type_bytes(meta));
517 opaque_modifier(
518 resolve::file_dialog_url_enabled_fn(),
519 resolve::file_dialog_url_enabled_oret(),
520 view,
521 &[value.as_ptr() as u64],
522 [0.0; 4],
523 )
524 }
525}