Skip to main content

densa_ui/
clipboard.rs

1//! Text copied and pasted between widgets and other applications.
2
3use std::fmt;
4
5/// Where copied text goes and pasted text comes from. A [`Ui`](crate::Ui) starts with a
6/// [`MemoryClipboard`], shared by its widgets only; a window gives it the system's
7/// ([`Ui::set_clipboard`](crate::Ui::set_clipboard)).
8pub trait Clipboard {
9    /// The text on the clipboard.
10    ///
11    /// # Errors
12    /// [`ClipboardError::Empty`] if it holds no text; [`ClipboardError::Unavailable`] if it
13    /// cannot be read.
14    fn text(&mut self) -> Result<String, ClipboardError>;
15
16    /// Puts `text` on the clipboard, replacing what it held.
17    ///
18    /// # Errors
19    /// [`ClipboardError::Unavailable`] if it cannot be written: what it held stays.
20    fn set_text(&mut self, text: &str) -> Result<(), ClipboardError>;
21}
22
23/// Why the clipboard gave no text, or took none.
24#[derive(Debug, Clone, PartialEq, Eq)]
25#[non_exhaustive]
26pub enum ClipboardError {
27    /// It holds no text (nothing, or only an image or files).
28    Empty,
29    /// The system would not let it be used, with its reason.
30    Unavailable(String),
31}
32
33impl fmt::Display for ClipboardError {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        match self {
36            Self::Empty => f.write_str("the clipboard holds no text"),
37            Self::Unavailable(reason) => write!(f, "the clipboard is unavailable: {reason}"),
38        }
39    }
40}
41
42impl std::error::Error for ClipboardError {}
43
44/// A clipboard in memory, seen by this process only: for tests and windowless interfaces.
45#[derive(Debug, Clone, Default)]
46pub struct MemoryClipboard {
47    text: Option<String>,
48}
49
50impl Clipboard for MemoryClipboard {
51    fn text(&mut self) -> Result<String, ClipboardError> {
52        self.text.clone().ok_or(ClipboardError::Empty)
53    }
54
55    fn set_text(&mut self, text: &str) -> Result<(), ClipboardError> {
56        self.text = Some(text.to_owned());
57        Ok(())
58    }
59}
60
61/// The clipboard a [`Ui`](crate::Ui) holds.
62pub(crate) struct Held(pub(crate) Box<dyn Clipboard>);
63
64impl Default for Held {
65    fn default() -> Self {
66        Self(Box::new(MemoryClipboard::default()))
67    }
68}
69
70impl fmt::Debug for Held {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        f.write_str("Clipboard")
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn a_memory_clipboard_gives_back_the_last_text_put_on_it() {
82        let mut clipboard = MemoryClipboard::default();
83        assert_eq!(clipboard.text(), Err(ClipboardError::Empty));
84        clipboard.set_text("one").unwrap();
85        clipboard.set_text("two").unwrap();
86        assert_eq!(clipboard.text().unwrap(), "two");
87        assert_eq!(clipboard.text().unwrap(), "two", "reading leaves it there");
88    }
89
90    #[test]
91    fn errors_say_what_went_wrong() {
92        assert_eq!(
93            ClipboardError::Empty.to_string(),
94            "the clipboard holds no text"
95        );
96        let busy = ClipboardError::Unavailable("busy".into());
97        assert_eq!(busy.to_string(), "the clipboard is unavailable: busy");
98    }
99}