Skip to main content

swiftui_native/views/
dragdrop.rs

1//! Drag, drop, and the item providers they carry.
2//!
3//! `NSItemProvider` is an Objective-C class, so these are the bindings where the
4//! two runtimes meet: SwiftUI hands over providers built by AppKit, and what
5//! goes back is built here through the Objective-C runtime.
6
7use super::*;
8use super::closures::*;
9use crate::abi;
10use crate::resolve;
11use core::ffi::c_void;
12use crate::mangle::{self, swift_type};
13
14/// Apply `.onDragSessionUpdated(_:)`.
15pub fn on_drag_session_updated(view: &ViewHandle, action: impl Fn() + 'static) -> ViewHandle {
16    unsafe {
17        opaque_modifier(
18            resolve::on_drag_session_updated_fn(),
19            resolve::on_drag_session_updated_oret(),
20            view,
21            &ignoring_value_words(action),
22            [0.0; 4],
23        )
24    }
25}
26
27/// Apply `.onDropSessionUpdated(_:)`.
28pub fn on_drop_session_updated(view: &ViewHandle, action: impl Fn() + 'static) -> ViewHandle {
29    unsafe {
30        opaque_modifier(
31            resolve::on_drop_session_updated_fn(),
32            resolve::on_drop_session_updated_oret(),
33            view,
34            &ignoring_value_words(action),
35            [0.0; 4],
36        )
37    }
38}
39
40/// A Swift `Array<UTType>`, as the one word it travels in.
41pub(crate) unsafe fn content_types(kinds: &[ContentType]) -> u64 {
42    unsafe {
43        let meta = crate::type_by_name!(swift_type!(UniformTypeIdentifiers.UTType).as_bytes());
44        let stride = abi::type_bytes(meta);
45        let mut bytes = vec![0u8; stride * kinds.len()];
46        for (i, k) in kinds.iter().enumerate() {
47            // `UTType` is a resilient struct, so its getter writes through x8
48            // even though the value it holds is a single reference.
49            let v = resilient_static(meta, k.getter());
50            bytes[i * stride..i * stride + v.len()].copy_from_slice(&v);
51        }
52        abi::swift_array(meta, kinds.len(), &bytes)
53    }
54}
55
56/// An `NSItemProvider` SwiftUI handed to a callback, or one built to hand back.
57///
58/// Reading what a provider *carries* is asynchronous — that is the whole point
59/// of the class — so what a synchronous Rust callback can see is which type
60/// identifiers it offers, which is a plain property.
61#[derive(Clone, Copy, Debug)]
62pub struct ItemProvider(pub(crate) *mut c_void);
63
64impl ItemProvider {
65    /// The underlying `NSItemProvider`.
66    pub fn as_ptr(&self) -> *mut c_void {
67        self.0
68    }
69
70    /// The uniform type identifiers this provider can produce.
71    pub fn type_identifiers(&self) -> Vec<String> {
72        unsafe {
73            type Send0 = unsafe extern "C" fn(*mut c_void, *const c_void) -> *mut c_void;
74            let send: Send0 = core::mem::transmute(crate::objc::msg_send_addr());
75            let array = send(self.0, crate::objc::sel(c"registeredTypeIdentifiers"));
76            if array.is_null() {
77                return Vec::new();
78            }
79            type Count = unsafe extern "C" fn(*mut c_void, *const c_void) -> usize;
80            let count: Count = core::mem::transmute(crate::objc::msg_send_addr());
81            let n = count(array, crate::objc::sel(c"count"));
82            type At = unsafe extern "C" fn(*mut c_void, *const c_void, usize) -> *mut c_void;
83            let at: At = core::mem::transmute(crate::objc::msg_send_addr());
84            type Utf8 = unsafe extern "C" fn(*mut c_void, *const c_void) -> *const i8;
85            let utf8: Utf8 = core::mem::transmute(crate::objc::msg_send_addr());
86            (0..n)
87                .map(|i| {
88                    let s = at(array, crate::objc::sel(c"objectAtIndex:"), i);
89                    let p = utf8(s, crate::objc::sel(c"UTF8String"));
90                    if p.is_null() {
91                        String::new()
92                    } else {
93                        core::ffi::CStr::from_ptr(p).to_string_lossy().into_owned()
94                    }
95                })
96                .collect()
97        }
98    }
99
100    /// Ask for the item's text. The answer arrives **later**, on the main queue.
101    ///
102    /// A drop handler could see how many items were dropped and what type
103    /// identifiers they carried, and nothing about what they actually were —
104    /// which is most of the way to not having a drop handler.
105    ///
106    /// Asynchronous because it has to be, not as a matter of taste: measured
107    /// with a probe that waited a full second without turning the run loop, the
108    /// completion never came, and the same load with the loop running returned
109    /// immediately. `NSItemProvider` delivers on the main queue, so a
110    /// synchronous read from a drop handler — which is already running on the
111    /// main thread inside AppKit's own dispatch — would wait for a block that
112    /// cannot run until it returns. Swift's `loadItem` is `async` for the same
113    /// reason.
114    ///
115    /// ```no_run
116    /// # use swiftui_native::prelude::*;
117    /// # use swiftui_native::views::ContentType;
118    /// let drop = text("drop here").on_drop(&[ContentType::PlainText], |providers| {
119    ///     for p in providers {
120    ///         p.load_plain_text(|text| println!("dropped {text:?}"));
121    ///     }
122    ///     true
123    /// });
124    /// ```
125    pub fn load_plain_text(&self, then: impl FnOnce(Option<String>) + 'static) {
126        unsafe {
127            let boxed: Box<dyn FnOnce(Option<String>)> = Box::new(then);
128            let capture = Box::into_raw(Box::new(boxed)) as *mut c_void;
129            // Leaked: the receiver may copy the block and call the copy, and
130            // either way it outlives this call. It is five words.
131            let block = Box::leak(Box::new(crate::objc::CapturingBlock::new(
132                loaded_item as *const c_void,
133                capture,
134            ))) as *const crate::objc::CapturingBlock;
135            let kind = crate::objc::nsstring("public.plain-text");
136            type Load = unsafe extern "C" fn(
137                *mut c_void, *const c_void, *mut c_void, *mut c_void,
138                *const crate::objc::CapturingBlock,
139            ) -> *mut c_void;
140            let load: Load = core::mem::transmute(crate::objc::msg_send_addr());
141            load(
142                self.0,
143                crate::objc::sel(c"loadItemForTypeIdentifier:options:completionHandler:"),
144                kind,
145                core::ptr::null_mut(),
146                block,
147            );
148        }
149    }
150
151    /// Build a provider carrying `text` as plain text.
152    pub fn plain_text(text: &str) -> Self {
153        unsafe {
154            let cls = crate::objc::class_named(c"NSItemProvider");
155            type Alloc = unsafe extern "C" fn(*mut c_void, *const c_void) -> *mut c_void;
156            let alloc: Alloc = core::mem::transmute(crate::objc::msg_send_addr());
157            let obj = alloc(cls, crate::objc::sel(c"alloc"));
158            let ns_string = crate::objc::class_named(c"NSString");
159            type With = unsafe extern "C" fn(*mut c_void, *const c_void, *const i8) -> *mut c_void;
160            let with: With = core::mem::transmute(crate::objc::msg_send_addr());
161            let c = std::ffi::CString::new(text).expect("text");
162            let value = with(ns_string, crate::objc::sel(c"stringWithUTF8String:"), c.as_ptr());
163            let kind = with(
164                ns_string,
165                crate::objc::sel(c"stringWithUTF8String:"),
166                c"public.plain-text".as_ptr(),
167            );
168            type Init = unsafe extern "C" fn(
169                *mut c_void, *const c_void, *mut c_void, *mut c_void,
170            ) -> *mut c_void;
171            let init: Init = core::mem::transmute(crate::objc::msg_send_addr());
172            let p = init(obj, crate::objc::sel(c"initWithItem:typeIdentifier:"), value, kind);
173            assert!(!p.is_null(), "NSItemProvider for {text:?}");
174            Self(p)
175        }
176    }
177}
178
179/// Read a Swift `[NSItemProvider]` back into Rust handles.
180pub(crate) unsafe fn read_provider_array(array: u64) -> Vec<ItemProvider> {
181    unsafe {
182        if array == 0 {
183            return Vec::new();
184        }
185        let stride = abi::type_bytes(crate::type_by_name!(swift_type!(objc NSItemProvider).as_bytes()));
186        let (base, count) = abi::array_parts(array);
187        (0..count)
188            .map(|i| ItemProvider(*(base.add(i * stride) as *const *mut c_void)))
189            .collect()
190    }
191}
192
193/// Apply `.onCopyCommand(perform:)`, offering the given plain-text items.
194pub fn on_copy_command(view: &ViewHandle, items: &[&str]) -> ViewHandle {
195    unsafe {
196        let c = word_closure(emit_providers, provider_payload(provider_array(items), None, None));
197        opaque_modifier(
198            resolve::on_copy_command_fn(),
199            resolve::on_copy_command_oret(),
200            view,
201            &c,
202            [0.0; 4],
203        )
204    }
205}
206
207/// Apply `.onCutCommand(perform:)`, offering the given plain-text items.
208pub fn on_cut_command(view: &ViewHandle, items: &[&str]) -> ViewHandle {
209    unsafe {
210        let c = word_closure(emit_providers, provider_payload(provider_array(items), None, None));
211        opaque_modifier(
212            resolve::on_cut_command_fn(),
213            resolve::on_cut_command_oret(),
214            view,
215            &c,
216            [0.0; 4],
217        )
218    }
219}
220
221/// Apply `.itemProvider(_:)` with a provider carrying `item`.
222pub fn item_provider(view: &ViewHandle, item: Option<&str>) -> ViewHandle {
223    unsafe {
224        let array = provider_array(&item.map_or(Vec::new(), |t| vec![t]));
225        let c = word_closure(emit_first_provider, provider_payload(array, None, None));
226        opaque_modifier(
227            resolve::item_provider_fn(),
228            resolve::item_provider_oret(),
229            view,
230            &c,
231            [0.0; 4],
232        )
233    }
234}
235
236/// Apply `.importsItemProviders(_:onImport:)`.
237pub fn imports_item_providers(
238    view: &ViewHandle,
239    kinds: &[ContentType],
240    accept: impl Fn(&[ItemProvider]) -> bool + 'static,
241) -> ViewHandle {
242    unsafe {
243        let types = content_types(kinds);
244        let c = word_closure(decide_on_providers, provider_payload(0, Some(Box::new(accept)), None));
245        opaque_modifier(
246            resolve::imports_item_providers_fn(),
247            resolve::imports_item_providers_oret(),
248            view,
249            &[types, c[0], c[1]],
250            [0.0; 4],
251        )
252    }
253}
254
255/// Apply `.onPasteCommand(of:perform:)`.
256pub fn on_paste_command(
257    view: &ViewHandle,
258    kinds: &[ContentType],
259    paste: impl Fn(&[ItemProvider]) + 'static,
260) -> ViewHandle {
261    unsafe {
262        let types = content_types(kinds);
263        let c = wide_words(observe_providers, provider_payload(0, None, Some(Box::new(paste))));
264        opaque_modifier(
265            resolve::on_paste_command_fn(),
266            resolve::on_paste_command_oret(),
267            view,
268            &[types, c[0], c[1]],
269            [0.0; 4],
270        )
271    }
272}
273
274/// Apply `.exportsItemProviders(_:onExport:onEdit:)`.
275///
276/// `items` are offered as plain text; `edit` is asked whether an edit was
277/// accepted.
278pub fn exports_item_providers(
279    view: &ViewHandle,
280    kinds: &[ContentType],
281    items: &[&str],
282    edit: impl Fn(&[ItemProvider]) -> bool + 'static,
283) -> ViewHandle {
284    unsafe {
285        let types = content_types(kinds);
286        let export =
287            word_closure(emit_providers, provider_payload(provider_array(items), None, None));
288        // `onEdit: ([NSItemProvider]) -> Bool` — one argument in x0, the answer
289        // back in x0. A dump cannot tell a wrong closure shape from a right one
290        // (both print `(Function)`), so this one is taken from the SDK rather
291        // than from the mangled name, which does not distinguish them either.
292        let on_edit = word_closure(decide_on_providers, provider_payload(0, Some(Box::new(edit)), None));
293        opaque_modifier(
294            resolve::exports_item_providers_fn(),
295            resolve::exports_item_providers_oret(),
296            view,
297            &[types, export[0], export[1], on_edit[0], on_edit[1]],
298            [0.0; 4],
299        )
300    }
301}
302
303/// Apply `.onDrop(of:isTargeted:perform:)`.
304///
305/// `accept` sees the dropped providers and answers whether it took them.
306pub fn on_drop(
307    view: &ViewHandle,
308    kinds: &[ContentType],
309    accept: impl Fn(&[ItemProvider]) -> bool + 'static,
310) -> ViewHandle {
311    unsafe {
312        let types = content_types(kinds);
313        // `Binding<Bool>?` is a `Binding` with a null location, so it is still
314        // three registers rather than an address.
315        let payload = abi::resolve_type_by_mangled_name(mangle::bound("7SwiftUI7BindingV", &[mangle::BOOL]).as_bytes());
316        let opt = abi::resolve_type_by_mangled_name(mangle::optional(&mangle::bound("7SwiftUI7BindingV", &[mangle::BOOL])).as_bytes());
317        let mut nil = vec![0u8; abi::type_bytes(opt).max(24)];
318        abi::store_optional_tag(&mut nil, payload, false);
319        let b = binding_words(&mut nil);
320        let perform = word_closure(decide_on_providers, provider_payload(0, Some(Box::new(accept)), None));
321        opaque_modifier(
322            resolve::on_drop_fn(),
323            resolve::on_drop_oret(),
324            view,
325            &[types, b[0], b[1], b[2], perform[0], perform[1]],
326            [0.0; 4],
327        )
328    }
329}
330
331/// What a drop does with what it is given.
332#[derive(Clone, Copy, Debug, PartialEq, Eq)]
333pub enum DropOperation {
334    Cancel = 0,
335    Forbidden = 1,
336    Copy = 2,
337    Move = 3,
338}
339
340/// `(DropSession) -> DropConfiguration` — build a fresh configuration.
341///
342/// `DropConfiguration` is resilient, so it comes back through x8; the session in
343/// x0 is not read, since deciding from it would mean reading a type that only
344/// exists while a drag is in flight.
345pub(crate) unsafe extern "C" fn emit_drop_configuration(out: *mut u8, payload: *mut c_void) {
346    unsafe {
347        let f = boxed_ref::<dyn Fn() -> DropOperation>(payload);
348        let op = abi::enum_argument(abi::metadata0(resolve::drop_operation_ma()), f() as u32);
349        let meta = abi::metadata0(resolve::drop_configuration_ma());
350        core::arch::asm!("blr {f}", f = in(reg) resolve::drop_configuration_init(),
351            in("x0") op, in("x8") out, in("x20") meta,
352            lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
353            lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x9") _,
354            lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
355            lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
356            lateout("lr") _, lateout("x21") _, clobber_abi("C"),
357        );
358    }
359}
360
361/// Apply `.dropConfiguration(_:)`.
362pub fn drop_configuration(view: &ViewHandle, decide: impl Fn() -> DropOperation + 'static) -> ViewHandle {
363    unsafe {
364        let ctx = Box::leak(Box::new(abi::ClosureCtx::new(
365            emit_drop_configuration,
366            boxed_ctx(Box::new(decide) as Box<dyn Fn() -> DropOperation>),
367        ))) as *mut abi::ClosureCtx;
368        opaque_modifier(
369            resolve::drop_configuration_fn(),
370            resolve::drop_configuration_oret(),
371            view,
372            &[abi::swift_closure_thunk as *const () as u64, ctx as u64],
373            [0.0; 4],
374        )
375    }
376}
377
378/// Which drag operations a view permits.
379#[derive(Clone, Copy, Debug, PartialEq, Eq)]
380pub struct DragOperations {
381    pub copy: bool,
382    pub move_: bool,
383    pub delete: bool,
384}
385
386/// Build one of the two `DragConfiguration.Operations*` values.
387///
388/// Both have the same three-`Bool` initialiser and both are resilient, so each
389/// comes back through x8 with its own metatype in x20.
390unsafe fn drag_operations(ma: *const c_void, init: *const c_void, ops: DragOperations) -> Vec<u8> {
391    unsafe {
392        let meta = abi::metadata0(ma);
393        let mut out = vec![0u8; abi::type_bytes(meta)];
394        abi::call_out(
395            init,
396            abi::ints(&[u64::from(ops.copy), u64::from(ops.move_), u64::from(ops.delete)]),
397            [0.0; 4],
398            meta as u64,
399            out.as_mut_ptr(),
400        );
401        out
402    }
403}
404
405/// Apply `.dragConfiguration(_:)`.
406///
407/// Three nested resilient values, each built by its own initialiser: the two
408/// `Operations` sets go in by address, and the configuration they make comes
409/// back by address to be handed to the modifier the same way.
410pub fn drag_configuration(
411    view: &ViewHandle,
412    within_app: DragOperations,
413    outside_app: DragOperations,
414) -> ViewHandle {
415    unsafe {
416        let within = drag_operations(
417            resolve::drag_ops_within_ma(), resolve::drag_ops_within_init(), within_app);
418        let outside = drag_operations(
419            resolve::drag_ops_outside_ma(), resolve::drag_ops_outside_init(), outside_app);
420        let meta = abi::metadata0(resolve::drag_configuration_ma());
421        let mut config = vec![0u8; abi::type_bytes(meta)];
422        core::arch::asm!("blr {f}", f = in(reg) resolve::drag_configuration_init(),
423            in("x0") within.as_ptr(), in("x1") outside.as_ptr(),
424            in("x8") config.as_mut_ptr(), in("x20") meta,
425            lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
426            lateout("x6") _, lateout("x7") _, lateout("x9") _, lateout("x10") _,
427            lateout("x11") _, lateout("x12") _, lateout("x13") _, lateout("x14") _,
428            lateout("x15") _, lateout("x16") _, lateout("x17") _, lateout("lr") _,
429            lateout("x21") _, clobber_abi("C"),
430        );
431        opaque_modifier(
432            resolve::drag_configuration_fn(),
433            resolve::drag_configuration_oret(),
434            view,
435            &[config.as_ptr() as u64],
436            [0.0; 4],
437        )
438    }
439}
440
441/// `() -> NSItemProvider` — hand back a provider built from a Rust string.
442pub(crate) unsafe extern "C" fn emit_provider(payload: *mut c_void, _arg: u64) -> u64 {
443    unsafe {
444        let text = &*(payload as *const String);
445        ItemProvider::plain_text(text).0 as u64
446    }
447}
448
449/// Apply `.onDrag(_:preview:)`.
450///
451/// The public spelling has two arguments; the symbol has six, because Swift
452/// fills the rest in from defaults at every call site. So the defaults are
453/// written out here: enabled, no local context, and neither session callback.
454///
455/// `Any?` is four words — three of inline existential buffer and one of type
456/// metadata — which is what pushes this past eight argument registers and makes
457/// it the widest modifier bound here.
458pub fn on_drag(view: &ViewHandle, item: &str, preview: &ViewHandle) -> ViewHandle {
459    unsafe {
460        let text = Box::leak(Box::new(item.to_string())) as *mut String;
461        let data = word_closure(emit_provider, text as *mut c_void);
462        // `Optional<Any>` holding `.none`. An existential is address-only —
463        // it holds an arbitrary value, so nothing can copy it without its
464        // witness table — which makes it one pointer here rather than the four
465        // words its thirty-two-byte layout would suggest. An all-zero container
466        // is `.none`, because the discriminator is the null metadata word.
467        let none_any = [0u64; 4];
468        let mut ints = vec![1u64, none_any.as_ptr() as u64];
469        ints.extend_from_slice(&data);
470        // Both session callbacks nil — a null function pointer is `.none` for
471        // an optional closure, so the context word beside it is never read.
472        ints.extend_from_slice(&[0, 0, 0, 0]);
473        ints.extend_from_slice(&view_words(preview));
474        generic1_modifier_ints(
475            resolve::on_drag_fn(),
476            resolve::on_drag_oret(),
477            view,
478            preview.meta,
479            preview.wt,
480            &ints,
481            [0.0; 4],
482        )
483    }
484}
485
486
487/// `void (^)(id item, NSError *error)` — turn the item into a Rust string and
488/// hand it to the waiting closure.
489///
490/// The item is whatever the provider registered: an `NSString` for text put
491/// there by [`ItemProvider::plain_text`], an `NSData` for most things dragged
492/// in from elsewhere. Both are handled, since a drop handler does not get to
493/// choose which it receives.
494unsafe extern "C" fn loaded_item(block: *mut c_void, item: *mut c_void, error: *mut c_void) {
495    unsafe {
496        let capture = (*(block as *const crate::objc::CapturingBlock)).capture;
497        if capture.is_null() {
498            return;
499        }
500        let then = Box::from_raw(capture as *mut Box<dyn FnOnce(Option<String>)>);
501        let text = (error.is_null() && !item.is_null())
502            .then(|| item_text(item))
503            .flatten();
504        then(text);
505    }
506}
507
508/// Read an `NSString` or an `NSData` as UTF-8.
509///
510/// # Safety
511/// `item` must be a live object.
512unsafe fn item_text(item: *mut c_void) -> Option<String> {
513    unsafe {
514        let _pool = crate::objc::AutoreleasePool::new();
515        // `UTF8String` first: an `NSString` answers it and an `NSData` does not,
516        // which is a cheaper question than asking each class by name.
517        if crate::objc::responds(item, crate::objc::sel(c"UTF8String")) {
518            let p = crate::objc::send(item, crate::objc::sel(c"UTF8String")) as *const i8;
519            if p.is_null() {
520                return None;
521            }
522            return Some(core::ffi::CStr::from_ptr(p).to_string_lossy().into_owned());
523        }
524        if crate::objc::responds(item, crate::objc::sel(c"bytes")) {
525            let bytes = crate::objc::send(item, crate::objc::sel(c"bytes")) as *const u8;
526            let len = crate::objc::send(item, crate::objc::sel(c"length")) as usize;
527            if bytes.is_null() || len == 0 {
528                return None;
529            }
530            return Some(String::from_utf8_lossy(core::slice::from_raw_parts(bytes, len)).into_owned());
531        }
532        None
533    }
534}