Skip to main content

ferroui_base/input/platform/
clipboard_extensions.rs

1use super::{ClipboardError, IClipboard};
2use crate::input::{
3    AsyncDataTransferExtensions, DataFormat, DataFormatOf, DataTransfer, DataTransferItem, IAsyncDataTransfer,
4    LocalBoxFuture,
5};
6use crate::media::imaging::Bitmap;
7use crate::platform::storage::IStorageItem;
8use std::rc::Rc;
9
10/// Disposes the data transfer returned by the clipboard when the operation
11/// reading from it ends, however it ends.
12struct Using(Rc<dyn IAsyncDataTransfer>);
13
14impl Drop for Using {
15    fn drop(&mut self) {
16        self.0.dispose();
17    }
18}
19
20/// Contains extension methods related to [`IClipboard`].
21///
22/// Every operation resolves to the [`ClipboardError`] of the clipboard or
23/// of the data transfer it reads from, where the reference throws from the
24/// awaited task.
25pub trait ClipboardExtensions: IClipboard {
26    /// Returns a list containing the formats currently available from the
27    /// clipboard. It is empty if the clipboard is empty.
28    fn get_data_formats_async(&self) -> LocalBoxFuture<Result<Rc<[DataFormat]>, ClipboardError>> {
29        let data = self.try_get_data_async();
30
31        Box::pin(async move {
32            match data.await? {
33                Some(data_transfer) => {
34                    let data_transfer = Using(data_transfer);
35                    data_transfer.0.try_formats()
36                }
37                None => Ok(Rc::from(Vec::new())),
38            }
39        })
40    }
41
42    /// Tries to get a value for a given format from the clipboard. The
43    /// future resolves to `None` if the format is not supported.
44    ///
45    /// If the clipboard contains several items supporting the format, the
46    /// first matching one will be returned.
47    fn try_get_value_async<T: Clone + 'static>(
48        &self,
49        format: &DataFormatOf<T>,
50    ) -> LocalBoxFuture<Result<Option<T>, ClipboardError>> {
51        let data = self.try_get_data_async();
52        let format = format.clone();
53
54        Box::pin(async move {
55            let Some(data_transfer) = data.await? else {
56                return Ok(None);
57            };
58            let data_transfer = Using(data_transfer);
59            let result = data_transfer.0.try_get_value_async(&format).await;
60            result
61        })
62    }
63
64    /// Tries to get multiple values for a given format from the clipboard.
65    /// The future resolves to `None` if the format is not supported.
66    fn try_get_values_async<T: Clone + 'static>(
67        &self,
68        format: &DataFormatOf<T>,
69    ) -> LocalBoxFuture<Result<Option<Vec<T>>, ClipboardError>> {
70        let data = self.try_get_data_async();
71        let format = format.clone();
72
73        Box::pin(async move {
74            let Some(data_transfer) = data.await? else {
75                return Ok(None);
76            };
77            let data_transfer = Using(data_transfer);
78            let result = data_transfer.0.try_get_values_async(&format).await;
79            result
80        })
81    }
82
83    /// Places a single value on the clipboard in the specified format.
84    ///
85    /// By calling this method, the clipboard will be instantly cleared, and
86    /// data will be lazily requested later when it is pasted.
87    ///
88    /// If `value` is `None`, nothing will be placed on the clipboard and it
89    /// will be cleared instead.
90    fn set_value_async<T: 'static>(
91        &self,
92        format: &DataFormatOf<T>,
93        value: Option<T>,
94    ) -> LocalBoxFuture<Result<(), ClipboardError>> {
95        let Some(value) = value else {
96            return self.clear_async();
97        };
98
99        let data_transfer = DataTransfer::new();
100        data_transfer.add(DataTransferItem::create(format, Some(value)));
101        self.set_data_async(Some(data_transfer))
102    }
103
104    /// Places multiple values on the clipboard in the specified format.
105    ///
106    /// By calling this method, the clipboard will be instantly cleared, and
107    /// data will be lazily requested later when it is pasted.
108    ///
109    /// If `values` is `None` or empty, nothing will be placed on the
110    /// clipboard and it will be cleared instead.
111    fn set_values_async<T: 'static>(
112        &self,
113        format: &DataFormatOf<T>,
114        values: Option<impl IntoIterator<Item = T>>,
115    ) -> LocalBoxFuture<Result<(), ClipboardError>>
116    where
117        Self: Sized,
118    {
119        let Some(values) = values else {
120            return self.clear_async();
121        };
122
123        let data_transfer = DataTransfer::new();
124
125        for value in values {
126            data_transfer.add(DataTransferItem::create(format, Some(value)));
127        }
128
129        if data_transfer.items().is_empty() {
130            self.clear_async()
131        } else {
132            self.set_data_async(Some(data_transfer))
133        }
134    }
135
136    /// Returns a text, if available, from the clipboard.
137    fn try_get_text_async(&self) -> LocalBoxFuture<Result<Option<String>, ClipboardError>> {
138        self.try_get_value_async(&DataFormat::text())
139    }
140
141    /// Returns a file (or folder), if available, from the clipboard.
142    fn try_get_file_async(&self) -> LocalBoxFuture<Result<Option<Rc<dyn IStorageItem>>, ClipboardError>> {
143        self.try_get_value_async(&DataFormat::file())
144    }
145
146    /// Returns the files (or folders), if available, from the clipboard.
147    fn try_get_files_async(&self) -> LocalBoxFuture<Result<Option<Vec<Rc<dyn IStorageItem>>>, ClipboardError>> {
148        self.try_get_values_async(&DataFormat::file())
149    }
150
151    /// Returns a bitmap, if available, from the clipboard.
152    fn try_get_bitmap_async(&self) -> LocalBoxFuture<Result<Option<Rc<Bitmap>>, ClipboardError>> {
153        self.try_get_value_async(&DataFormat::bitmap())
154    }
155
156    /// Places a text on the clipboard.
157    ///
158    /// By calling this method, the clipboard will be instantly cleared.
159    ///
160    /// If `text` is `None`, nothing will be placed on the clipboard and it
161    /// will be cleared instead.
162    fn set_text_async(&self, text: Option<&str>) -> LocalBoxFuture<Result<(), ClipboardError>> {
163        self.set_value_async(&DataFormat::text(), text.map(str::to_string))
164    }
165
166    /// Places a file (or folder) on the clipboard.
167    ///
168    /// By calling this method, the clipboard will be instantly cleared.
169    ///
170    /// If `file` is `None`, nothing will be placed on the clipboard and it
171    /// will be cleared instead.
172    fn set_file_async(&self, file: Option<Rc<dyn IStorageItem>>) -> LocalBoxFuture<Result<(), ClipboardError>> {
173        self.set_value_async(&DataFormat::file(), file)
174    }
175
176    /// Places a list of files (or folders) on the clipboard.
177    ///
178    /// By calling this method, the clipboard will be instantly cleared.
179    ///
180    /// If `files` is `None` or empty, nothing will be placed on the
181    /// clipboard and it will be cleared instead.
182    fn set_files_async(
183        &self,
184        files: Option<impl IntoIterator<Item = Rc<dyn IStorageItem>>>,
185    ) -> LocalBoxFuture<Result<(), ClipboardError>>
186    where
187        Self: Sized,
188    {
189        self.set_values_async(&DataFormat::file(), files)
190    }
191
192    /// Places a bitmap on the clipboard.
193    ///
194    /// By calling this method, the clipboard will be instantly cleared.
195    ///
196    /// If `bitmap` is `None`, nothing will be placed on the clipboard and
197    /// it will be cleared instead.
198    fn set_bitmap_async(&self, bitmap: Option<Rc<Bitmap>>) -> LocalBoxFuture<Result<(), ClipboardError>> {
199        self.set_value_async(&DataFormat::bitmap(), bitmap)
200    }
201}
202
203impl<I: IClipboard + ?Sized> ClipboardExtensions for I {}