sequoia-elements 0.1.0

Sequoia GUI framework — built-in UI elements
Documentation
use std::any::{Any, TypeId};
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;

pub trait Clipboard {
    fn get_text(&self) -> Option<String>;

    fn set_text(&self, text: &str);
}

#[derive(Clone)]
pub struct SharedClipboard(pub Rc<dyn Clipboard>);

#[derive(Clone, Default)]
pub struct MemClipboard {
    text: Rc<RefCell<String>>,
}

impl MemClipboard {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn content(&self) -> String {
        self.text.borrow().clone()
    }
}

impl Clipboard for MemClipboard {
    fn get_text(&self) -> Option<String> {
        let text = self.text.borrow();
        if text.is_empty() {
            None
        } else {
            Some(text.clone())
        }
    }

    fn set_text(&self, text: &str) {
        *self.text.borrow_mut() = text.to_owned();
    }
}

#[derive(Clone, Default)]
pub struct DataClipboard {
    slots: Rc<RefCell<HashMap<TypeId, Rc<dyn Any>>>>,
}

impl DataClipboard {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn set<T: Clone + 'static>(&self, value: T) {
        self.slots
            .borrow_mut()
            .insert(TypeId::of::<T>(), Rc::new(value));
    }

    pub fn get<T: Clone + 'static>(&self) -> Option<T> {
        self.slots
            .borrow()
            .get(&TypeId::of::<T>())?
            .downcast_ref::<T>()
            .cloned()
    }

    pub fn clear<T: 'static>(&self) -> bool {
        self.slots.borrow_mut().remove(&TypeId::of::<T>()).is_some()
    }
}

#[derive(Clone)]
pub struct SharedDataClipboard(pub Rc<DataClipboard>);

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn mem_clipboard_roundtrip() {
        let clip = MemClipboard::new();
        assert_eq!(clip.get_text(), None, "empty at start");
        clip.set_text("hello");
        assert_eq!(clip.get_text().as_deref(), Some("hello"));
        assert_eq!(clip.content(), "hello");
        let shared = SharedClipboard(Rc::new(clip.clone()));
        shared.0.set_text("shared");
        assert_eq!(clip.get_text().as_deref(), Some("shared"), "same clipboard");
    }

    #[test]
    fn data_clipboard_is_typed() {
        #[derive(Clone, PartialEq, Debug)]
        struct Layer(String);
        #[derive(Clone, PartialEq, Debug)]
        struct Other(i32);
        let clip = DataClipboard::new();
        assert_eq!(clip.get::<Layer>(), None, "empty at start");
        clip.set(Layer("hero".into()));
        clip.set(Other(7));
        assert_eq!(clip.get::<Layer>(), Some(Layer("hero".into())));
        assert_eq!(clip.get::<Other>(), Some(Other(7)));

        clip.set(Layer("footer".into()));
        assert_eq!(clip.get::<Layer>(), Some(Layer("footer".into())));
        assert!(clip.clear::<Layer>());
        assert_eq!(clip.get::<Layer>(), None);
        assert!(!clip.clear::<Layer>());

        let shared = SharedDataClipboard(Rc::new(clip.clone()));
        shared.0.set(Other(9));
        assert_eq!(clip.get::<Other>(), Some(Other(9)), "same slots");
    }
}