Skip to main content

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}