rat_text/
clipboard.rs

1//!
2//! There are too many clipboard crates.
3//!
4//! Provides the Clipboard trait to connect all text-widgets
5//! with the clipboard crate of your choice.
6//!
7//! There is a default implementation that allows copying
8//! within the application.
9//!
10
11use crate::TextError;
12use dyn_clone::{clone_box, DynClone};
13use std::error::Error;
14use std::fmt::{Debug, Display, Formatter};
15use std::sync::{Arc, Mutex, OnceLock};
16
17#[derive(Debug)]
18pub struct ClipboardError;
19
20impl Display for ClipboardError {
21    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
22        write!(f, "{:?}", self)
23    }
24}
25
26impl Error for ClipboardError {}
27
28impl From<ClipboardError> for TextError {
29    fn from(_value: ClipboardError) -> Self {
30        TextError::Clipboard
31    }
32}
33
34/// Access some clipboard.
35pub trait Clipboard: DynClone + Debug {
36    /// Get text from the clipboard.
37    fn get_string(&self) -> Result<String, ClipboardError>;
38
39    /// Set text to the clipboard.
40    fn set_string(&self, s: &str) -> Result<(), ClipboardError>;
41}
42
43static GLOBAL_CLIPBOARD: OnceLock<StaticClipboard> = OnceLock::new();
44
45/// Get a Clone of the global default clipboard.
46pub fn global_clipboard() -> Box<dyn Clipboard> {
47    let c = GLOBAL_CLIPBOARD.get_or_init(StaticClipboard::default);
48    Box::new(c.clone())
49}
50
51/// Change the global default clipboard.
52pub fn set_global_clipboard(clipboard: impl Clipboard + Send + 'static) {
53    let c = GLOBAL_CLIPBOARD.get_or_init(StaticClipboard::default);
54    c.replace(clipboard);
55}
56
57/// Clipboard that can be set as a static.
58/// It can replace the actual clipboard implementation at a later time.
59/// Initializes with a LocalClipboard.
60#[derive(Debug, Clone)]
61struct StaticClipboard {
62    clip: Arc<Mutex<Box<dyn Clipboard + Send>>>,
63}
64
65impl Default for StaticClipboard {
66    fn default() -> Self {
67        Self {
68            clip: Arc::new(Mutex::new(Box::new(LocalClipboard::new()))),
69        }
70    }
71}
72
73impl StaticClipboard {
74    /// Replace the static clipboard with the given one.
75    fn replace(&self, clipboard: impl Clipboard + Send + 'static) {
76        let mut clip = self.clip.lock().expect("clipboard-lock");
77        *clip = Box::new(clipboard);
78    }
79}
80
81impl Clipboard for StaticClipboard {
82    fn get_string(&self) -> Result<String, ClipboardError> {
83        self.clip.lock().expect("clipboard-lock").get_string()
84    }
85
86    fn set_string(&self, s: &str) -> Result<(), ClipboardError> {
87        self.clip.lock().expect("clipboard-lock").set_string(s)
88    }
89}
90
91/// Local clipboard.
92/// A string in disguise.
93#[derive(Debug, Default, Clone)]
94pub struct LocalClipboard {
95    text: Arc<Mutex<String>>,
96}
97
98impl LocalClipboard {
99    pub fn new() -> Self {
100        Self::default()
101    }
102}
103
104impl Clipboard for LocalClipboard {
105    fn get_string(&self) -> Result<String, ClipboardError> {
106        match self.text.lock() {
107            Ok(v) => Ok(v.clone()),
108            Err(_) => Err(ClipboardError),
109        }
110    }
111
112    fn set_string(&self, s: &str) -> Result<(), ClipboardError> {
113        match self.text.lock() {
114            Ok(mut v) => {
115                *v = s.to_string();
116                Ok(())
117            }
118            Err(_) => Err(ClipboardError),
119        }
120    }
121}
122
123impl Clone for Box<dyn Clipboard> {
124    fn clone(&self) -> Self {
125        clone_box(self.as_ref())
126    }
127}
128
129impl Clipboard for Box<dyn Clipboard> {
130    fn get_string(&self) -> Result<String, ClipboardError> {
131        self.as_ref().get_string()
132    }
133
134    fn set_string(&self, s: &str) -> Result<(), ClipboardError> {
135        self.as_ref().set_string(s)
136    }
137}