Skip to main content

ferroui_base/input/
async_data_transfer_extensions.rs

1use super::async_to_sync_data_transfer::AsyncToSyncDataTransfer;
2use super::platform::ClipboardError;
3use super::sync_to_async_data_transfer::SyncToAsyncDataTransfer;
4use super::{
5    AsyncDataTransferItemExtensions, DataFormat, DataFormatOf, IAsyncDataTransfer, IAsyncDataTransferItem,
6    IDataTransfer, LocalBoxFuture,
7};
8use crate::logging::{LogEventLevel, Logger};
9use crate::media::imaging::Bitmap;
10use crate::platform::storage::IStorageItem;
11use std::rc::Rc;
12
13/// The items of an asynchronous data transfer that support a format, in
14/// order.
15pub struct AsyncDataTransferItems {
16    items: Rc<[Rc<dyn IAsyncDataTransferItem>]>,
17    format: DataFormat,
18    index: usize,
19}
20
21impl Iterator for AsyncDataTransferItems {
22    type Item = Rc<dyn IAsyncDataTransferItem>;
23
24    fn next(&mut self) -> Option<Self::Item> {
25        while let Some(item) = self.items.get(self.index) {
26            self.index += 1;
27            if item.contains(&self.format) {
28                return Some(item.clone());
29            }
30        }
31        None
32    }
33}
34
35impl dyn IAsyncDataTransfer {
36    /// Returns the data transfer as a synchronous one: itself if it also
37    /// implements [`IDataTransfer`], a wrapper waiting for the values
38    /// otherwise.
39    ///
40    /// This is an implementation detail of the platform backends.
41    pub fn to_synchronous(self: Rc<Self>, log_area: &'static str) -> Rc<dyn IDataTransfer> {
42        if let Some(data_transfer) = self.clone().as_data_transfer() {
43            return data_transfer;
44        }
45
46        if let Some(logger) = Logger::try_get(LogEventLevel::Warning, log_area) {
47            logger.log(
48                None,
49                "Using a synchronous wrapper for IAsyncDataTransferItem. Consider implementing IDataTransfer instead.",
50            );
51        }
52
53        AsyncToSyncDataTransfer::new(self)
54    }
55}
56
57impl dyn IDataTransfer {
58    /// Returns the data transfer as an asynchronous one: itself if it also
59    /// implements [`IAsyncDataTransfer`], a wrapper otherwise.
60    ///
61    /// This is an implementation detail of the platform backends.
62    pub fn to_asynchronous(self: Rc<Self>) -> Rc<dyn IAsyncDataTransfer> {
63        match self.clone().as_async_data_transfer() {
64            Some(data_transfer) => data_transfer,
65            None => SyncToAsyncDataTransfer::new(self),
66        }
67    }
68}
69
70// Keep the `try_get_xxx_async` methods in sync with the `try_get_xxx` ones
71// of `DataTransferExtensions`.
72
73/// Contains extension methods for [`IAsyncDataTransfer`].
74pub trait AsyncDataTransferExtensions: IAsyncDataTransfer {
75    /// Gets whether the data transfer supports a specific format.
76    fn contains(&self, format: &DataFormat) -> bool {
77        self.formats().iter().any(|candidate| candidate == format)
78    }
79
80    /// Gets the list of [`IAsyncDataTransferItem`] contained in the data
81    /// transfer, filtered by a given format.
82    ///
83    /// Some platforms (such as Windows and X11) may only support a single
84    /// data item for all formats except files.
85    fn get_items(&self, format: &DataFormat) -> AsyncDataTransferItems {
86        AsyncDataTransferItems { items: self.items(), format: format.clone(), index: 0 }
87    }
88
89    /// Tries to get a value for a given format from the data transfer. The
90    /// future resolves to `None` if the format is not supported, and to an
91    /// error if the platform providing the items, their formats or the
92    /// value fails.
93    ///
94    /// If the data transfer contains several items supporting the format,
95    /// the first matching one will be returned.
96    fn try_get_value_async<T: Clone + 'static>(
97        &self,
98        format: &DataFormatOf<T>,
99    ) -> LocalBoxFuture<Result<Option<T>, ClipboardError>> {
100        // The first item supporting the format is looked for before this
101        // returns, as in the reference; the items after it are not asked
102        // for their formats.
103        let item = self.try_items().and_then(|items| {
104            for item in items.iter() {
105                if item.try_contains(format)? {
106                    return Ok(Some(item.clone()));
107                }
108            }
109            Ok(None)
110        });
111
112        match item {
113            Ok(Some(item)) => item.try_get_value_async(format),
114            Ok(None) => Box::pin(std::future::ready(Ok(None))),
115            Err(error) => Box::pin(std::future::ready(Err(error))),
116        }
117    }
118
119    /// Tries to get multiple values for a given format from the data
120    /// transfer. The future resolves to `None` if the format is not
121    /// supported, and to an error as soon as the platform providing the
122    /// items, their formats or one of the values fails.
123    fn try_get_values_async<T: Clone + 'static>(
124        &self,
125        format: &DataFormatOf<T>,
126    ) -> LocalBoxFuture<Result<Option<Vec<T>>, ClipboardError>> {
127        let items = self.try_items();
128        let format = format.clone();
129
130        Box::pin(async move {
131            let mut results: Option<Vec<T>> = None;
132
133            // The items are queried one after the other, each on the thread
134            // the future is polled on; an item is asked for its formats
135            // when the items before it have delivered their values.
136            for item in items?.iter() {
137                if !item.try_contains(&format)? {
138                    continue;
139                }
140
141                let Some(result) = item.try_get_value_async(&format).await? else { continue };
142                results.get_or_insert_with(Vec::new).push(result);
143            }
144
145            Ok(results)
146        })
147    }
148
149    /// Returns a text, if available, from the data transfer.
150    ///
151    /// If the data transfer contains several items supporting
152    /// [`DataFormat::text`], the first matching one will be returned.
153    fn try_get_text_async(&self) -> LocalBoxFuture<Result<Option<String>, ClipboardError>> {
154        self.try_get_value_async(&DataFormat::text())
155    }
156
157    /// Returns a bitmap, if available, from the data transfer.
158    ///
159    /// If the data transfer contains several items supporting
160    /// [`DataFormat::bitmap`], the first matching one will be returned.
161    fn try_get_bitmap_async(&self) -> LocalBoxFuture<Result<Option<Rc<Bitmap>>, ClipboardError>> {
162        self.try_get_value_async(&DataFormat::bitmap())
163    }
164
165    /// Returns a file (or folder), if available, from the data transfer.
166    ///
167    /// See [`DataFormat::file`].
168    fn try_get_file_async(&self) -> LocalBoxFuture<Result<Option<Rc<dyn IStorageItem>>, ClipboardError>> {
169        self.try_get_value_async(&DataFormat::file())
170    }
171
172    /// Returns the files (or folders), if available, from the data
173    /// transfer.
174    ///
175    /// See [`DataFormat::file`].
176    fn try_get_files_async(&self) -> LocalBoxFuture<Result<Option<Vec<Rc<dyn IStorageItem>>>, ClipboardError>> {
177        self.try_get_values_async(&DataFormat::file())
178    }
179}
180
181impl<I: IAsyncDataTransfer + ?Sized> AsyncDataTransferExtensions for I {}