use std::cell::RefCell;
use std::rc::Rc;
pub trait PlatformClipboard {
fn write_text(&self, text: &str);
fn read_text(&self) -> Option<String>;
}
pub(crate) struct ClipboardSessionState {
platform: RefCell<Option<Rc<dyn PlatformClipboard>>>,
fallback: RefCell<Option<String>>,
}
impl ClipboardSessionState {
pub(crate) fn new() -> Self {
Self {
platform: RefCell::new(None),
fallback: RefCell::new(None),
}
}
fn set_platform(&self, clipboard: Option<Rc<dyn PlatformClipboard>>) {
*self.platform.borrow_mut() = clipboard;
}
fn write(&self, text: &str) {
if let Some(platform) = self.platform.borrow().clone() {
platform.write_text(text);
} else {
*self.fallback.borrow_mut() = Some(text.to_string());
}
}
fn read(&self) -> Option<String> {
if let Some(platform) = self.platform.borrow().clone() {
platform.read_text()
} else {
self.fallback.borrow().clone()
}
}
fn has_platform(&self) -> bool {
self.platform.borrow().is_some()
}
}
pub fn set_platform_clipboard(clipboard: Rc<dyn PlatformClipboard>) {
crate::render_state::with_clipboard_session(|state| state.set_platform(Some(clipboard)));
}
pub fn clear_platform_clipboard() {
crate::render_state::with_clipboard_session(|state| state.set_platform(None));
}
pub fn clipboard_write_text(text: &str) {
crate::render_state::with_clipboard_session(|state| state.write(text));
}
pub fn clipboard_read_text() -> Option<String> {
crate::render_state::with_clipboard_session(|state| state.read())
}
pub fn has_platform_clipboard() -> bool {
crate::render_state::with_clipboard_session(|state| state.has_platform())
}
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
pub struct ClipboardManager;
impl ClipboardManager {
pub fn set_text(&self, text: &str) {
clipboard_write_text(text);
}
pub fn text(&self) -> Option<String> {
clipboard_read_text()
}
pub fn has_system_clipboard(&self) -> bool {
has_platform_clipboard()
}
}
pub fn local_clipboard() -> cranpose_core::CompositionLocal<ClipboardManager> {
thread_local! {
static LOCAL_CLIPBOARD: RefCell<Option<cranpose_core::CompositionLocal<ClipboardManager>>> =
const { RefCell::new(None) };
}
LOCAL_CLIPBOARD.with(|cell| {
cell.borrow_mut()
.get_or_insert_with(|| cranpose_core::compositionLocalOf(ClipboardManager::default))
.clone()
})
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::RefCell;
struct RecordingClipboard {
value: RefCell<Option<String>>,
}
impl PlatformClipboard for RecordingClipboard {
fn write_text(&self, text: &str) {
*self.value.borrow_mut() = Some(text.to_string());
}
fn read_text(&self) -> Option<String> {
self.value.borrow().clone()
}
}
#[test]
fn manager_uses_in_process_fallback_without_a_platform_clipboard() {
let context = crate::render_state::AppContext::new();
context.enter(|| {
let clipboard = ClipboardManager;
assert!(!clipboard.has_system_clipboard());
clipboard.set_text("hello");
assert_eq!(clipboard.text().as_deref(), Some("hello"));
});
}
#[test]
fn manager_routes_through_the_installed_platform_clipboard() {
let context = crate::render_state::AppContext::new();
context.enter(|| {
let recorder = Rc::new(RecordingClipboard {
value: RefCell::new(None),
});
set_platform_clipboard(recorder.clone());
let clipboard = ClipboardManager;
assert!(clipboard.has_system_clipboard());
clipboard.set_text("world");
assert_eq!(recorder.value.borrow().as_deref(), Some("world"));
assert_eq!(clipboard.text().as_deref(), Some("world"));
clear_platform_clipboard();
assert!(!clipboard.has_system_clipboard());
});
}
}