i_slint_core/data_transfer.rs
1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4//! Types and helpers related to the [`DataTransfer`] type, which implements type-indexed arbitrary
5//! data transfer both within an application and between applications.
6
7use alloc::rc::Rc;
8use core::any::Any;
9
10use crate::{SharedString, api::Image};
11
12#[cfg(feature = "ffi")]
13pub mod ffi;
14
15/// Hidden type to make `DataTransfer` smaller and easier to use in FFI.
16///
17/// In particular, `Image` is a different size depending on feature flags, so this
18/// allows `DataTransfer` to have a normalized size no matter what flags are enabled.
19#[derive(Default, Clone, PartialEq)]
20struct DataTransferInner {
21 // TODO: Custom binary data providers with custom MIME types.
22 /// Special-cased support for images, as the precise implementation of transferring
23 /// images differs between platforms.
24 image: Option<Image>,
25 /// Special-cased support for plain text, as the precise implementation of transferring
26 /// text differs between platforms.
27 plain_text: Option<SharedString>,
28}
29
30/// `DataTransfer` abstracts over the various ways of transferring data within an application
31/// and between applications.
32///
33/// The details will depend on the current platform, but the common features are:
34///
35/// - Each `DataTransfer` contains multiple views over the same data in different formats
36/// - The `DataTransfer` may contain an in-memory representation of the data, which can be
37/// sent and received within the current application
38/// - Serializing to/deserializing from a given format may be done eagerly or lazily[^lazy-note]
39///
40/// [^lazy-note]: Platforms differ on which formats can and cannot be lazy, but all support it in
41/// some capacity. Reading data from a `DataTransfer` cannot be assumed to be a cheap operation.
42///
43/// Currently, only plain text and image data is supported. Precisely how this maps to the
44/// backend will depend on platform and features. Work to expand this API is ongoing, see
45/// [the tracking issue for drag-and-drop][dnd-tracking-issue] to follow its progress.
46///
47/// [dnd-tracking-issue]: https://github.com/slint-ui/slint/issues/1967
48///
49/// The easiest way to construct this type is with the [`Default`] implementation, followed
50/// by [`set_plain_text`](DataTransfer::set_plain_text) or [`set_image`](DataTransfer::set_image).
51/// There are also implementations of [`From<SharedString>`](SharedString) and [`From<Image>`](Image)
52/// which construct a new `DataTransfer` using those methods respectively. The opposites of these
53/// operations are [`plain_text`](DataTransfer::plain_text) and
54/// [`image`](DataTransfer::image).
55///
56/// ```rust
57/// # use i_slint_core::{DataTransfer, string::ToSharedString as _};
58///
59/// let message = "Hello, world!";
60/// let data = DataTransfer::from(message.to_shared_string());
61/// assert_eq!(data.plain_text().unwrap(), message);
62/// ```
63#[derive(Clone, Default)]
64#[repr(C)]
65pub struct DataTransfer {
66 /// Special-cased types. `Option<Rc>` to prevent allocating if this `DataTransfer`
67 /// only contains `user_data`.
68 inner: Option<Rc<DataTransferInner>>,
69 /// A custom in-memory value. No MIME type-based dispatch is done here - if the user
70 /// wants to store one of a set of possible values, they should store their own enum
71 /// and handle the dispatch themselves.
72 user_data: Option<Rc<dyn Any>>,
73}
74
75impl core::fmt::Debug for DataTransfer {
76 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
77 f.debug_struct("DataTransfer")
78 .field("has_plain_text", &self.has_plain_text())
79 .field("has_image", &self.has_image())
80 .field("has_user_data", &self.user_data.is_some())
81 .finish()
82 }
83}
84
85// `PartialEq` doesn't really make sense for `DataTransfer`, but since it's required for values
86// that Slint interacts with, we can at least make a best-effort attempt. This will return true
87// if `other` is an unmodified clone of `self`, but if any modification has been done to either
88// value since cloning then this will return false even if the two values are semantically
89// identical.
90impl PartialEq for DataTransfer {
91 fn eq(&self, other: &Self) -> bool {
92 self.inner == other.inner
93 && self.user_data.as_ref().map(Rc::as_ptr) == other.user_data.as_ref().map(Rc::as_ptr)
94 }
95}
96
97impl From<SharedString> for DataTransfer {
98 fn from(value: SharedString) -> Self {
99 let mut out = DataTransfer::default();
100
101 out.set_plain_text(value);
102
103 out
104 }
105}
106
107impl From<Image> for DataTransfer {
108 fn from(value: Image) -> Self {
109 let mut out = DataTransfer::default();
110
111 out.set_image(value);
112
113 out
114 }
115}
116
117/// An error which can occur while fetching data from a `DataTransfer`.
118#[derive(Debug, Clone)]
119#[non_exhaustive]
120pub enum DataTransferError {
121 /// The type was not listed in the set of available MIME types.
122 TypeNotFound,
123}
124
125impl core::error::Error for DataTransferError {}
126
127impl core::fmt::Display for DataTransferError {
128 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
129 match self {
130 Self::TypeNotFound => {
131 write!(f, "Type not supplied by data transfer")
132 }
133 }
134 }
135}
136
137impl DataTransfer {
138 /// Sets an image to be transferred by this [`DataTransfer`].
139 ///
140 /// The image can be read using [`image`](DataTransfer::image). If
141 /// you only need the [`DataTransfer`] to have an image representation, use
142 /// [`From<Image>`](Image).
143 ///
144 /// Each [`DataTransfer`] can only have a single image set at once. If this
145 /// method is called multiple times, the previous image will be overwritten.
146 /// However, you can have, for example, both an image representation and a
147 /// plain text representation set simultaneously on the same [`DataTransfer`].
148 ///
149 /// Passing a default-constructed `Image` clears the previously-set image
150 /// instead of storing it, so afterwards [`has_image`](DataTransfer::has_image)
151 /// returns `false`. If the resulting `DataTransfer` carries no plain text,
152 /// image, or user data, it compares equal to [`DataTransfer::default()`].
153 pub fn set_image(&mut self, image: Image) -> &mut Self {
154 if matches!(image.0, crate::ImageInner::None) {
155 self.clear_image();
156 } else {
157 Rc::make_mut(self.inner.get_or_insert_default()).image = Some(image);
158 }
159 self
160 }
161
162 /// Sets unstyled, basic text to be transferred by this [`DataTransfer`].
163 ///
164 /// The image can be read using [`plain_text`](DataTransfer::plain_text).
165 /// If you only need the [`DataTransfer`] to have a plain text representation,
166 /// use [`From<SharedString>`](SharedString).
167 ///
168 /// Each [`DataTransfer`] can only have a single plain text representation
169 /// set at once. If this method is called multiple times, the previous text
170 /// will be overwritten. However, you can have, for example, both an image
171 /// representation and a plain text representation set simultaneously on the
172 /// same [`DataTransfer`].
173 ///
174 /// Passing an empty string clears the previously-set plain text instead of
175 /// storing it, so afterwards [`has_plain_text`](DataTransfer::has_plain_text)
176 /// returns `false`. If the resulting `DataTransfer` carries no plain text,
177 /// image, or user data, it compares equal to [`DataTransfer::default()`].
178 pub fn set_plain_text(&mut self, plain_text: SharedString) -> &mut Self {
179 if plain_text.is_empty() {
180 self.clear_plain_text();
181 } else {
182 Rc::make_mut(self.inner.get_or_insert_default()).plain_text = Some(plain_text);
183 }
184 self
185 }
186
187 fn clear_image(&mut self) {
188 let Some(inner_rc) = self.inner.as_mut() else { return };
189 if inner_rc.image.is_some() {
190 Rc::make_mut(inner_rc).image = None;
191 }
192 if inner_rc.image.is_none() && inner_rc.plain_text.is_none() {
193 self.inner = None;
194 }
195 }
196
197 fn clear_plain_text(&mut self) {
198 let Some(inner_rc) = self.inner.as_mut() else { return };
199 if inner_rc.plain_text.is_some() {
200 Rc::make_mut(inner_rc).plain_text = None;
201 }
202 if inner_rc.image.is_none() && inner_rc.plain_text.is_none() {
203 self.inner = None;
204 }
205 }
206
207 /// Returns `true` if this data transfer advertises that it is readable as an [`Image`].
208 ///
209 /// This does not necessarily mean that `image` will return `Ok`, as an I/O error
210 /// may occur.
211 pub fn has_image(&self) -> bool {
212 self.inner.as_ref().is_some_and(|inner| inner.image.is_some())
213 }
214
215 /// Returns `true` if this data transfer advertises that it is readable as plain text.
216 ///
217 /// This does not necessarily mean that `plain_text` will return `Ok`, as an I/O
218 /// error may occur.
219 pub fn has_plain_text(&self) -> bool {
220 self.inner.as_ref().is_some_and(|inner| inner.plain_text.is_some())
221 }
222
223 /// Returns `true` if this data transfer carries no data: no plain text, no image,
224 /// and no user data. A `DataTransfer` constructed via [`Default::default`] is empty.
225 pub fn is_empty(&self) -> bool {
226 !self.has_plain_text() && !self.has_image() && self.user_data.is_none()
227 }
228
229 /// Set the application-internal data represented by this [`DataTransfer`].
230 /// This can be read with [`DataTransfer::user_data`], and allows circumventing
231 /// serialize/deserializing the data to bytes when a drag-and-drop or copy-paste
232 /// operation stays within the application.
233 pub fn set_user_data(&mut self, value: Rc<dyn Any>) -> &mut Self {
234 self.user_data = Some(value);
235 self
236 }
237
238 /// Helper to read this [`DataTransfer`] as plain text, supporting multiple encodings.
239 ///
240 /// The caller should assume that this method call may do I/O.
241 pub fn plain_text(&self) -> Result<SharedString, DataTransferError> {
242 self.inner
243 .as_ref()
244 .and_then(|inner| inner.plain_text.clone())
245 .ok_or(DataTransferError::TypeNotFound)
246 }
247
248 /// Helper to read this [`DataTransfer`] as an image, supporting multiple image types.
249 ///
250 /// The caller should assume that this method call may do I/O.
251 pub fn image(&self) -> Result<Image, DataTransferError> {
252 self.inner
253 .as_ref()
254 .and_then(|inner| inner.image.clone())
255 .ok_or(DataTransferError::TypeNotFound)
256 }
257
258 /// Get the application-internal data represented by this [`DataTransfer`], if
259 /// one exists.
260 pub fn user_data(&self) -> Option<Rc<dyn Any>> {
261 self.user_data.clone()
262 }
263}
264
265#[cfg(test)]
266mod tests {
267 use super::*;
268 use crate::graphics::{Rgba8Pixel, SharedPixelBuffer};
269
270 #[test]
271 fn set_plain_text_with_empty_string_clears() {
272 let mut dt = DataTransfer::default();
273 dt.set_plain_text("hello".into());
274 assert!(dt.has_plain_text());
275 dt.set_plain_text("".into());
276 assert!(!dt.has_plain_text());
277 assert!(dt.plain_text().is_err());
278 }
279
280 #[test]
281 fn set_image_with_default_image_clears() {
282 let mut dt = DataTransfer::default();
283 let buffer = SharedPixelBuffer::<Rgba8Pixel>::new(2, 2);
284 dt.set_image(Image::from_rgba8(buffer));
285 assert!(dt.has_image());
286 dt.set_image(Image::default());
287 assert!(!dt.has_image());
288 assert!(dt.image().is_err());
289 }
290
291 #[test]
292 fn set_plain_text_with_empty_string_on_default_stays_empty() {
293 let mut dt = DataTransfer::default();
294 dt.set_plain_text("".into());
295 assert!(!dt.has_plain_text());
296 assert!(dt.is_empty());
297 // The clear path on an already-default transfer must not allocate an
298 // inner Rc — otherwise it would compare unequal to `default()` even
299 // though both are observably empty.
300 assert!(dt.inner.is_none());
301 assert_eq!(dt, DataTransfer::default());
302 }
303
304 #[test]
305 fn set_image_with_default_image_on_default_stays_empty() {
306 let mut dt = DataTransfer::default();
307 dt.set_image(Image::default());
308 assert!(!dt.has_image());
309 assert!(dt.is_empty());
310 assert!(dt.inner.is_none());
311 assert_eq!(dt, DataTransfer::default());
312 }
313
314 #[test]
315 fn cleared_transfer_compares_equal_to_default() {
316 // After clearing every field, the inner Rc must be released so the
317 // transfer compares equal to a freshly-constructed default.
318 let mut dt = DataTransfer::default();
319 dt.set_plain_text("hello".into());
320 dt.set_image(Image::from_rgba8(SharedPixelBuffer::<Rgba8Pixel>::new(2, 2)));
321 assert!(!dt.is_empty());
322 dt.set_plain_text("".into());
323 assert!(dt.inner.is_some(), "image still set, inner must remain");
324 dt.set_image(Image::default());
325 assert!(dt.is_empty());
326 assert!(dt.inner.is_none());
327 assert_eq!(dt, DataTransfer::default());
328 }
329}