use alloc::rc::Rc;
use core::any::Any;
#[cfg(feature = "std")]
use crate::SharedVector;
use crate::{SharedString, api::Image};
#[cfg(feature = "ffi")]
pub mod ffi;
#[derive(Default, Clone, PartialEq)]
struct DataTransferInner {
image: Option<Image>,
plain_text: Option<SharedString>,
#[cfg(feature = "std")]
file_paths: Option<SharedVector<std::path::PathBuf>>,
}
#[derive(Clone, Default)]
#[repr(C)]
pub struct DataTransfer {
inner: Option<Rc<DataTransferInner>>,
user_data: Option<Rc<dyn Any>>,
}
impl core::fmt::Debug for DataTransfer {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let mut s = f.debug_struct("DataTransfer");
s.field("has_plain_text", &self.has_plain_text()).field("has_image", &self.has_image());
#[cfg(feature = "std")]
s.field("has_file_paths", &self.has_file_paths());
s.field("has_user_data", &self.user_data.is_some());
s.finish()
}
}
impl PartialEq for DataTransfer {
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner
&& self.user_data.as_ref().map(Rc::as_ptr) == other.user_data.as_ref().map(Rc::as_ptr)
}
}
impl From<SharedString> for DataTransfer {
fn from(value: SharedString) -> Self {
let mut out = DataTransfer::default();
out.set_plain_text(value);
out
}
}
impl From<Image> for DataTransfer {
fn from(value: Image) -> Self {
let mut out = DataTransfer::default();
out.set_image(value);
out
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum DataTransferError {
TypeNotFound,
}
impl core::error::Error for DataTransferError {}
impl core::fmt::Display for DataTransferError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::TypeNotFound => {
write!(f, "Type not supplied by data transfer")
}
}
}
}
impl DataTransfer {
pub fn set_image(&mut self, image: Image) -> &mut Self {
if matches!(image.0, crate::ImageInner::None) {
self.clear_image();
} else {
Rc::make_mut(self.inner.get_or_insert_default()).image = Some(image);
}
self
}
pub fn set_plain_text(&mut self, plain_text: SharedString) -> &mut Self {
if plain_text.is_empty() {
self.clear_plain_text();
} else {
Rc::make_mut(self.inner.get_or_insert_default()).plain_text = Some(plain_text);
}
self
}
#[cfg(feature = "std")]
pub fn set_file_paths<I>(&mut self, file_paths: I) -> &mut Self
where
I: IntoIterator,
I::Item: AsRef<std::path::Path>,
{
self.set_file_paths_vec(
file_paths.into_iter().map(|path| path.as_ref().to_path_buf()).collect(),
);
self
}
#[cfg(feature = "std")]
fn set_file_paths_vec(&mut self, file_paths: SharedVector<std::path::PathBuf>) {
if file_paths.is_empty() {
self.clear_file_paths();
} else {
Rc::make_mut(self.inner.get_or_insert_default()).file_paths = Some(file_paths);
}
}
fn clear_image(&mut self) {
let Some(inner_rc) = self.inner.as_mut() else { return };
if inner_rc.image.is_some() {
Rc::make_mut(inner_rc).image = None;
}
self.release_empty_inner();
}
fn clear_plain_text(&mut self) {
let Some(inner_rc) = self.inner.as_mut() else { return };
if inner_rc.plain_text.is_some() {
Rc::make_mut(inner_rc).plain_text = None;
}
self.release_empty_inner();
}
#[cfg(feature = "std")]
fn clear_file_paths(&mut self) {
let Some(inner_rc) = self.inner.as_mut() else { return };
if inner_rc.file_paths.is_some() {
Rc::make_mut(inner_rc).file_paths = None;
}
self.release_empty_inner();
}
fn release_empty_inner(&mut self) {
if self.inner.as_ref().is_some_and(|inner| **inner == DataTransferInner::default()) {
self.inner = None;
}
}
pub(crate) fn has_native_data(&self) -> bool {
self.inner.is_some()
}
pub fn has_image(&self) -> bool {
self.inner.as_ref().is_some_and(|inner| inner.image.is_some())
}
pub fn has_plain_text(&self) -> bool {
self.inner.as_ref().is_some_and(|inner| inner.plain_text.is_some())
}
#[cfg(feature = "std")]
pub fn has_file_paths(&self) -> bool {
self.inner.as_ref().is_some_and(|inner| inner.file_paths.is_some())
}
pub fn is_empty(&self) -> bool {
self.inner.is_none() && self.user_data.is_none()
}
pub fn set_user_data(&mut self, value: Rc<dyn Any>) -> &mut Self {
self.user_data = Some(value);
self
}
pub fn plain_text(&self) -> Result<SharedString, DataTransferError> {
self.inner
.as_ref()
.and_then(|inner| inner.plain_text.clone())
.ok_or(DataTransferError::TypeNotFound)
}
pub fn image(&self) -> Result<Image, DataTransferError> {
self.inner
.as_ref()
.and_then(|inner| inner.image.clone())
.ok_or(DataTransferError::TypeNotFound)
}
#[cfg(feature = "std")]
pub fn file_paths(
&self,
) -> Result<impl Iterator<Item = &std::path::Path> + '_, DataTransferError> {
self.inner
.as_ref()
.and_then(|inner| inner.file_paths.as_ref())
.map(|paths| paths.iter().map(std::path::PathBuf::as_path))
.ok_or(DataTransferError::TypeNotFound)
}
pub fn user_data(&self) -> Option<Rc<dyn Any>> {
self.user_data.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graphics::{Rgba8Pixel, SharedPixelBuffer};
#[test]
fn set_plain_text_with_empty_string_clears() {
let mut dt = DataTransfer::default();
dt.set_plain_text("hello".into());
assert!(dt.has_plain_text());
dt.set_plain_text("".into());
assert!(!dt.has_plain_text());
assert!(dt.plain_text().is_err());
}
#[test]
fn set_image_with_default_image_clears() {
let mut dt = DataTransfer::default();
let buffer = SharedPixelBuffer::<Rgba8Pixel>::new(2, 2);
dt.set_image(Image::from_rgba8(buffer));
assert!(dt.has_image());
dt.set_image(Image::default());
assert!(!dt.has_image());
assert!(dt.image().is_err());
}
#[test]
fn set_plain_text_with_empty_string_on_default_stays_empty() {
let mut dt = DataTransfer::default();
dt.set_plain_text("".into());
assert!(!dt.has_plain_text());
assert!(dt.is_empty());
assert!(dt.inner.is_none());
assert_eq!(dt, DataTransfer::default());
}
#[test]
fn set_image_with_default_image_on_default_stays_empty() {
let mut dt = DataTransfer::default();
dt.set_image(Image::default());
assert!(!dt.has_image());
assert!(dt.is_empty());
assert!(dt.inner.is_none());
assert_eq!(dt, DataTransfer::default());
}
#[test]
fn cleared_transfer_compares_equal_to_default() {
let mut dt = DataTransfer::default();
dt.set_plain_text("hello".into());
dt.set_image(Image::from_rgba8(SharedPixelBuffer::<Rgba8Pixel>::new(2, 2)));
dt.set_file_paths(["/tmp/a"]);
assert!(!dt.is_empty());
dt.set_plain_text("".into());
assert!(dt.inner.is_some(), "image and files still set, inner must remain");
dt.set_image(Image::default());
assert!(dt.inner.is_some(), "files still set, inner must remain");
dt.set_file_paths(core::iter::empty::<&str>());
assert!(dt.is_empty());
assert!(dt.inner.is_none());
assert_eq!(dt, DataTransfer::default());
}
#[test]
fn every_payload_counts_as_native_data() {
assert!(!DataTransfer::default().has_native_data());
for set in [
(|dt: &mut DataTransfer| {
dt.set_plain_text("hello".into());
}) as fn(&mut DataTransfer),
|dt| {
dt.set_image(Image::from_rgba8(SharedPixelBuffer::<Rgba8Pixel>::new(2, 2)));
},
|dt| {
dt.set_file_paths(["/tmp/a"]);
},
] {
let mut dt = DataTransfer::default();
set(&mut dt);
assert!(dt.has_native_data());
}
}
#[test]
fn set_file_paths_with_empty_list_clears() {
use std::path::Path;
use std::vec::Vec;
let mut dt = DataTransfer::default();
dt.set_file_paths(["/tmp/a", "/tmp/b"]);
assert!(dt.has_file_paths());
assert_eq!(
dt.file_paths().unwrap().collect::<Vec<_>>(),
[Path::new("/tmp/a"), Path::new("/tmp/b")]
);
dt.set_file_paths(core::iter::empty::<&str>());
assert!(!dt.has_file_paths());
assert!(dt.file_paths().is_err());
assert!(dt.inner.is_none());
assert_eq!(dt, DataTransfer::default());
}
}