ferroui_native/
ferro_native_drag_source.rs1use 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#[derive(Default)]
18pub struct FerroNativeDragSource;
19
20impl FerroNativeDragSource {
21 pub fn new() -> Self {
22 Self
23 }
24}
25
26#[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
72pub(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
79pub(crate) unsafe fn data_transfer_from_handle(handle: *mut c_void) -> Option<Rc<dyn IDataTransfer>> {
84 unsafe { (handle as *const Rc<dyn IDataTransfer>).as_ref() }.cloned()
86}
87
88pub(crate) unsafe fn free_data_transfer_handle(handle: *mut c_void) {
94 if !handle.is_null() {
95 drop(unsafe { Box::from_raw(handle as *mut Rc<dyn IDataTransfer>) });
97 }
98}
99
100impl IPlatformDragSource for FerroNativeDragSource {
101 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 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 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 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}