Skip to main content

ferroui_native/
ferro_native_drag_source.rs

1use crate::data_transfer_to_frn_clipboard_data_source_wrapper::DataTransferToFrnClipboardDataSourceWrapper;
2use crate::helpers::to_frn_point;
3use crate::interop::*;
4use crate::top_level_impl::top_level_of;
5use ferroui_base::input::platform::IPlatformDragSource;
6use ferroui_base::input::{DragDropEffects, IDataTransfer, LocalBoxFuture, PointerPressedEventArgs};
7use ferroui_base::{Ref, Visual};
8use ferroui_controls::TopLevel;
9use std::cell::RefCell;
10use std::ffi::c_void;
11use std::future::Future;
12use std::pin::Pin;
13use std::rc::Rc;
14use std::task::{Context, Poll, Waker};
15
16/// Starts native drag-and-drop operations.
17#[derive(Default)]
18pub struct FerroNativeDragSource;
19
20impl FerroNativeDragSource {
21    pub fn new() -> Self {
22        Self
23    }
24}
25
26/// The outcome of a drag-and-drop operation, set once by the native side.
27#[derive(Default)]
28struct Completion {
29    result: Option<DragDropEffects>,
30    waker: Option<Waker>,
31}
32
33struct CompletionFuture(Rc<RefCell<Completion>>);
34
35impl Future for CompletionFuture {
36    type Output = DragDropEffects;
37
38    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<DragDropEffects> {
39        let mut completion = self.0.borrow_mut();
40        match completion.result {
41            Some(result) => Poll::Ready(result),
42            None => {
43                completion.waker = Some(cx.waker().clone());
44                Poll::Pending
45            }
46        }
47    }
48}
49
50struct DndCallback {
51    completion: Rc<RefCell<Completion>>,
52}
53
54impl IFrnDndResultCallbackImpl for DndCallback {
55    fn on_drag_and_drop_complete(&self, effect: FrnDragDropEffects) {
56        crate::callback_base::guard((), || {
57            let waker = {
58                let mut completion = self.completion.borrow_mut();
59                if completion.result.is_some() {
60                    return;
61                }
62                completion.result = Some(DragDropEffects::from_bits_retain(effect.0));
63                completion.waker.take()
64            };
65            if let Some(waker) = waker {
66                waker.wake();
67            }
68        })
69    }
70}
71
72/// The handle a data transfer travels through native code with: it comes
73/// back in the drag events of the top-level under the pointer and is freed
74/// through the handle deallocator of the platform.
75pub(crate) fn data_transfer_to_handle(data_transfer: Rc<dyn IDataTransfer>) -> *mut c_void {
76    Box::into_raw(Box::new(data_transfer)) as *mut c_void
77}
78
79/// The data transfer behind a handle made by [`data_transfer_to_handle`].
80///
81/// # Safety
82/// `handle` must be null or a live handle made by `data_transfer_to_handle`.
83pub(crate) unsafe fn data_transfer_from_handle(handle: *mut c_void) -> Option<Rc<dyn IDataTransfer>> {
84    // SAFETY: per the contract, a non-null handle points at a live box.
85    unsafe { (handle as *const Rc<dyn IDataTransfer>).as_ref() }.cloned()
86}
87
88/// Frees a handle made by [`data_transfer_to_handle`].
89///
90/// # Safety
91/// `handle` must be null or a live handle made by `data_transfer_to_handle`
92/// that is not used afterwards.
93pub(crate) unsafe fn free_data_transfer_handle(handle: *mut c_void) {
94    if !handle.is_null() {
95        // SAFETY: per the contract, the handle is a box that is freed once.
96        drop(unsafe { Box::from_raw(handle as *mut Rc<dyn IDataTransfer>) });
97    }
98}
99
100impl IPlatformDragSource for FerroNativeDragSource {
101    /// # Panics
102    /// Panics when the source of the trigger event is not hosted by a
103    /// top-level of this backend.
104    fn do_drag_drop_async(
105        &self,
106        trigger_event: &PointerPressedEventArgs,
107        data_transfer: Rc<dyn IDataTransfer>,
108        allowed_effects: DragDropEffects,
109    ) -> LocalBoxFuture<DragDropEffects> {
110        // Sanity check
111        let source = trigger_event.source().and_then(|source| source.cast::<Visual>());
112        let tl = TopLevel::get_top_level(source.as_deref());
113        let view = tl
114            .as_ref()
115            .and_then(|tl| tl.platform_impl())
116            .and_then(|platform_impl| top_level_of(&*platform_impl).cloned());
117        let (Some(view), Some(tl)) = (view, tl) else {
118            panic!("Value does not fall within the expected range.");
119        };
120        let tl: Ref<Visual> = tl.upcast();
121
122        trigger_event.pointer().capture(None);
123
124        let completion = Rc::new(RefCell::new(Completion::default()));
125
126        let cb = IFrnDndResultCallback::from_impl(DndCallback { completion: completion.clone() });
127        let data_source = IFrnClipboardDataSource::from_impl(DataTransferToFrnClipboardDataSourceWrapper::new(
128            data_transfer.clone(),
129        ));
130        view.begin_dragging_session(
131            FrnDragDropEffects(allowed_effects.bits()),
132            to_frn_point(trigger_event.get_position(Some(&tl))),
133            &data_source,
134            &cb,
135            data_transfer_to_handle(data_transfer),
136        );
137
138        Box::pin(CompletionFuture(completion))
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145    use ferroui_base::input::platform::{PlatformDataTransfer, PlatformDataTransferImpl, PlatformDataTransferItem};
146    use ferroui_base::input::DataFormat;
147    use std::task::Wake;
148
149    struct Empty;
150    impl PlatformDataTransferImpl for Empty {
151        fn provide_formats(&self) -> Result<Vec<DataFormat>, ferroui_base::input::platform::ClipboardError> {
152            Ok(Vec::new())
153        }
154        fn provide_items(
155            &self,
156        ) -> Result<Vec<Rc<PlatformDataTransferItem>>, ferroui_base::input::platform::ClipboardError> {
157            Ok(Vec::new())
158        }
159        fn dispose(&self, _owner: &PlatformDataTransfer) {}
160    }
161
162    #[test]
163    fn data_transfer_handles_round_trip() {
164        let data_transfer: Rc<dyn IDataTransfer> = PlatformDataTransfer::new(Empty);
165        let handle = data_transfer_to_handle(data_transfer.clone());
166        assert_eq!(Rc::strong_count(&data_transfer), 2);
167
168        // SAFETY: the handle was just made and is freed once below.
169        let recovered = unsafe { data_transfer_from_handle(handle) }.expect("data transfer");
170        assert!(Rc::ptr_eq(&recovered, &data_transfer));
171        drop(recovered);
172        unsafe { free_data_transfer_handle(handle) };
173        assert_eq!(Rc::strong_count(&data_transfer), 1);
174
175        // SAFETY: null handles are accepted.
176        assert!(unsafe { data_transfer_from_handle(std::ptr::null_mut()) }.is_none());
177        unsafe { free_data_transfer_handle(std::ptr::null_mut()) };
178    }
179
180    struct NoopWaker;
181    impl Wake for NoopWaker {
182        fn wake(self: std::sync::Arc<Self>) {}
183    }
184
185    #[test]
186    fn the_operation_completes_with_the_first_reported_effect() {
187        let completion = Rc::new(RefCell::new(Completion::default()));
188        let callback = IFrnDndResultCallback::from_impl(DndCallback { completion: completion.clone() });
189        let mut future = Box::pin(CompletionFuture(completion));
190        let waker = Waker::from(std::sync::Arc::new(NoopWaker));
191        let mut context = Context::from_waker(&waker);
192
193        assert!(future.as_mut().poll(&mut context).is_pending());
194        callback.on_drag_and_drop_complete(FrnDragDropEffects::Move);
195        callback.on_drag_and_drop_complete(FrnDragDropEffects::Copy);
196        assert_eq!(future.as_mut().poll(&mut context), Poll::Ready(DragDropEffects::MOVE));
197    }
198}