Skip to main content

sequoia_elements/
clipboard.rs

1use std::any::{Any, TypeId};
2use std::cell::RefCell;
3use std::collections::HashMap;
4use std::rc::Rc;
5
6pub trait Clipboard {
7    fn get_text(&self) -> Option<String>;
8
9    fn set_text(&self, text: &str);
10}
11
12#[derive(Clone)]
13pub struct SharedClipboard(pub Rc<dyn Clipboard>);
14
15#[derive(Clone, Default)]
16pub struct MemClipboard {
17    text: Rc<RefCell<String>>,
18}
19
20impl MemClipboard {
21    pub fn new() -> Self {
22        Self::default()
23    }
24
25    pub fn content(&self) -> String {
26        self.text.borrow().clone()
27    }
28}
29
30impl Clipboard for MemClipboard {
31    fn get_text(&self) -> Option<String> {
32        let text = self.text.borrow();
33        if text.is_empty() {
34            None
35        } else {
36            Some(text.clone())
37        }
38    }
39
40    fn set_text(&self, text: &str) {
41        *self.text.borrow_mut() = text.to_owned();
42    }
43}
44
45#[derive(Clone, Default)]
46pub struct DataClipboard {
47    slots: Rc<RefCell<HashMap<TypeId, Rc<dyn Any>>>>,
48}
49
50impl DataClipboard {
51    pub fn new() -> Self {
52        Self::default()
53    }
54
55    pub fn set<T: Clone + 'static>(&self, value: T) {
56        self.slots
57            .borrow_mut()
58            .insert(TypeId::of::<T>(), Rc::new(value));
59    }
60
61    pub fn get<T: Clone + 'static>(&self) -> Option<T> {
62        self.slots
63            .borrow()
64            .get(&TypeId::of::<T>())?
65            .downcast_ref::<T>()
66            .cloned()
67    }
68
69    pub fn clear<T: 'static>(&self) -> bool {
70        self.slots.borrow_mut().remove(&TypeId::of::<T>()).is_some()
71    }
72}
73
74#[derive(Clone)]
75pub struct SharedDataClipboard(pub Rc<DataClipboard>);
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn mem_clipboard_roundtrip() {
83        let clip = MemClipboard::new();
84        assert_eq!(clip.get_text(), None, "empty at start");
85        clip.set_text("hello");
86        assert_eq!(clip.get_text().as_deref(), Some("hello"));
87        assert_eq!(clip.content(), "hello");
88        let shared = SharedClipboard(Rc::new(clip.clone()));
89        shared.0.set_text("shared");
90        assert_eq!(clip.get_text().as_deref(), Some("shared"), "same clipboard");
91    }
92
93    #[test]
94    fn data_clipboard_is_typed() {
95        #[derive(Clone, PartialEq, Debug)]
96        struct Layer(String);
97        #[derive(Clone, PartialEq, Debug)]
98        struct Other(i32);
99        let clip = DataClipboard::new();
100        assert_eq!(clip.get::<Layer>(), None, "empty at start");
101        clip.set(Layer("hero".into()));
102        clip.set(Other(7));
103        assert_eq!(clip.get::<Layer>(), Some(Layer("hero".into())));
104        assert_eq!(clip.get::<Other>(), Some(Other(7)));
105
106        clip.set(Layer("footer".into()));
107        assert_eq!(clip.get::<Layer>(), Some(Layer("footer".into())));
108        assert!(clip.clear::<Layer>());
109        assert_eq!(clip.get::<Layer>(), None);
110        assert!(!clip.clear::<Layer>());
111
112        let shared = SharedDataClipboard(Rc::new(clip.clone()));
113        shared.0.set(Other(9));
114        assert_eq!(clip.get::<Other>(), Some(Other(9)), "same slots");
115    }
116}