Skip to main content

ferroui_base/input/
data_format.rs

1use super::{DataFormatKind, DataFormatOf};
2use crate::media::imaging::Bitmap;
3use crate::platform::storage::IStorageItem;
4use std::any::TypeId;
5use std::fmt;
6use std::hash::{Hash, Hasher};
7use std::rc::Rc;
8
9/// Represents a format usable with the clipboard and drag-and-drop.
10///
11/// A format is a value: two formats are equal when their kind and their
12/// identifier are equal. The typed counterpart, which also carries the type
13/// of the data, is [`DataFormatOf`]; it dereferences to `DataFormat`.
14#[derive(Clone)]
15pub struct DataFormat {
16    kind: DataFormatKind,
17    identifier: Rc<str>,
18    /// The type of the values of the format, when the format was created
19    /// as a typed format (the type argument of the managed
20    /// `DataFormat<T>`). Not part of the identity of the format.
21    data_type: Option<TypeId>,
22}
23
24impl PartialEq for DataFormat {
25    fn eq(&self, other: &DataFormat) -> bool {
26        self.kind == other.kind && self.identifier == other.identifier
27    }
28}
29
30impl Eq for DataFormat {}
31
32impl Hash for DataFormat {
33    fn hash<H: Hasher>(&self, state: &mut H) {
34        self.kind.hash(state);
35        self.identifier.hash(state);
36    }
37}
38
39crate::ferro_thread_local! {
40    static TEXT: DataFormatOf<String> = DataFormat::create_universal_format("Text");
41    static BITMAP: DataFormatOf<Rc<Bitmap>> = DataFormat::create_universal_format("Bitmap");
42    static FILE: DataFormatOf<Rc<dyn IStorageItem>> = DataFormat::create_universal_format("File");
43}
44
45impl DataFormat {
46    pub(super) fn new(kind: DataFormatKind, identifier: &str, data_type: Option<TypeId>) -> Self {
47        Self { kind, identifier: Rc::from(identifier), data_type }
48    }
49
50    /// The type of the values of the format (`String` for a format of
51    /// strings, `Rc<[u8]>` for a format of bytes), when the format was
52    /// created with one: the counterpart of asking whether a format of the
53    /// managed original is a `DataFormat<string>` or a `DataFormat<byte[]>`,
54    /// which a platform backend does to decide how to read data the system
55    /// does not describe. Two formats that differ in it alone are equal.
56    #[inline]
57    pub fn data_type(&self) -> Option<TypeId> {
58        self.data_type
59    }
60
61    /// The kind of the data format.
62    #[inline]
63    pub fn kind(&self) -> DataFormatKind {
64        self.kind
65    }
66
67    /// The identifier of the data format.
68    #[inline]
69    pub fn identifier(&self) -> &str {
70        &self.identifier
71    }
72
73    /// A data format representing plain text. Its data type is [`String`].
74    pub fn text() -> DataFormatOf<String> {
75        TEXT.with(Clone::clone)
76    }
77
78    /// A data format representing a bitmap. Its data type is a shared
79    /// [`Bitmap`].
80    pub fn bitmap() -> DataFormatOf<Rc<Bitmap>> {
81        BITMAP.with(Clone::clone)
82    }
83
84    /// A data format representing a single file. Its data type is a shared
85    /// [`IStorageItem`].
86    pub fn file() -> DataFormatOf<Rc<dyn IStorageItem>> {
87        FILE.with(Clone::clone)
88    }
89
90    /// Creates a name for this format, usable by the underlying platform.
91    ///
92    /// `application_prefix` is the system prefix used to recognize the name
93    /// as an application format.
94    ///
95    /// # Panics
96    /// This method can only be called if [`kind`](Self::kind) is
97    /// [`DataFormatKind::Application`] or [`DataFormatKind::Platform`].
98    pub fn to_system_name(&self, application_prefix: &str) -> String {
99        match self.kind {
100            DataFormatKind::Application => {
101                let mut name = String::with_capacity(application_prefix.len() + self.identifier.len());
102                name.push_str(application_prefix);
103                name.push_str(&self.identifier);
104                name
105            }
106            DataFormatKind::Platform => self.identifier.to_string(),
107            _ => panic!("Cannot get system name for {} format {}", self.kind, self.identifier),
108        }
109    }
110
111    fn create_universal_format<T: 'static>(identifier: &str) -> DataFormatOf<T> {
112        DataFormatOf::new(DataFormatKind::Universal, identifier)
113    }
114
115    /// Creates a new format specific to the application that returns an
116    /// array of bytes.
117    ///
118    /// To avoid conflicts with system identifiers, `identifier` isn't
119    /// passed to the underlying platform directly. However, two different
120    /// applications using the same identifier with
121    /// `create_bytes_application_format` or
122    /// [`create_string_application_format`](Self::create_string_application_format)
123    /// are able to share data using this format.
124    ///
125    /// # Panics
126    /// Only ASCII letters (A-Z, a-z), digits (0-9), the dot (.) and the
127    /// hyphen (-) are accepted in the identifier, which must not be empty.
128    pub fn create_bytes_application_format(identifier: &str) -> DataFormatOf<Rc<[u8]>> {
129        Self::create_application_format(identifier)
130    }
131
132    /// Creates a new format specific to the application that returns a
133    /// [`String`].
134    ///
135    /// See [`create_bytes_application_format`](Self::create_bytes_application_format)
136    /// for the identifier.
137    pub fn create_string_application_format(identifier: &str) -> DataFormatOf<String> {
138        Self::create_application_format(identifier)
139    }
140
141    /// Creates a new format that stays within the current process and is
142    /// never serialized to a platform clipboard or drag-and-drop operation.
143    ///
144    /// `T` is the data type. `identifier` is only used for equality
145    /// comparisons within the process and is never passed to the underlying
146    /// platform.
147    ///
148    /// # Panics
149    /// Panics if the identifier is empty.
150    pub fn create_in_process_format<T: 'static>(identifier: &str) -> DataFormatOf<T> {
151        if identifier.is_empty() {
152            panic!("The value cannot be an empty string. (Parameter 'identifier')");
153        }
154
155        DataFormatOf::new(DataFormatKind::InProcess, identifier)
156    }
157
158    fn create_application_format<T: 'static>(identifier: &str) -> DataFormatOf<T> {
159        if !Self::is_valid_application_format_identifier(identifier) {
160            panic!("Invalid application identifier (Parameter 'identifier')");
161        }
162
163        DataFormatOf::new(DataFormatKind::Application, identifier)
164    }
165
166    /// Creates a new format for the current platform that returns an array
167    /// of bytes.
168    ///
169    /// `identifier` is not validated and is passed as is to the underlying
170    /// platform. Most systems use mime types, but macOS requires Uniform
171    /// Type Identifiers (UTI).
172    ///
173    /// # Panics
174    /// Panics if the identifier is empty.
175    pub fn create_bytes_platform_format(identifier: &str) -> DataFormatOf<Rc<[u8]>> {
176        Self::create_platform_format(identifier)
177    }
178
179    /// Creates a new format for the current platform that returns a
180    /// [`String`].
181    ///
182    /// See [`create_bytes_platform_format`](Self::create_bytes_platform_format)
183    /// for the identifier.
184    pub fn create_string_platform_format(identifier: &str) -> DataFormatOf<String> {
185        Self::create_platform_format(identifier)
186    }
187
188    fn create_platform_format<T: 'static>(identifier: &str) -> DataFormatOf<T> {
189        if identifier.is_empty() {
190            panic!("The value cannot be an empty string. (Parameter 'identifier')");
191        }
192
193        DataFormatOf::new(DataFormatKind::Platform, identifier)
194    }
195
196    /// Creates a format from a name coming from the underlying platform.
197    ///
198    /// `application_prefix` is the system prefix used to recognize the name
199    /// as an application format. This is an implementation detail of the
200    /// platform backends.
201    pub fn from_system_name<T: 'static>(system_name: &str, application_prefix: &str) -> DataFormatOf<T> {
202        let prefix_length = application_prefix.len();
203        let has_prefix = system_name
204            .as_bytes()
205            .get(..prefix_length)
206            .is_some_and(|start| start.eq_ignore_ascii_case(application_prefix.as_bytes()))
207            && system_name.is_char_boundary(prefix_length);
208
209        if has_prefix {
210            let identifier = &system_name[prefix_length..];
211            if Self::is_valid_application_format_identifier(identifier) {
212                return DataFormatOf::new(DataFormatKind::Application, identifier);
213            }
214        }
215
216        DataFormatOf::new(DataFormatKind::Platform, system_name)
217    }
218
219    fn is_valid_application_format_identifier(identifier: &str) -> bool {
220        !identifier.is_empty() && identifier.chars().all(|c| c.is_ascii_alphanumeric() || c == '.' || c == '-')
221    }
222}
223
224impl fmt::Display for DataFormat {
225    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226        write!(f, "{}: {}", self.kind, self.identifier)
227    }
228}
229
230impl fmt::Debug for DataFormat {
231    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
232        fmt::Display::fmt(self, f)
233    }
234}