Skip to main content

tessera_ui/
clipboard.rs

1//! Focused clipboard input and platform replies.
2//!
3//! ## Usage
4//!
5//! Route browser or platform clipboard actions through the focused editor.
6
7use std::{cell::RefCell, collections::VecDeque};
8
9use crate::{keyboard_state::next_input_sequence, runtime::schedule_runtime_redraw};
10
11/// An explicit clipboard action initiated by the user.
12#[derive(Clone, Debug, PartialEq, Eq)]
13pub enum ClipboardAction {
14    /// Copies the current selection.
15    Copy,
16    /// Copies and then removes the current selection after a successful write.
17    Cut,
18    /// Inserts plain text supplied by the platform paste event.
19    Paste(String),
20}
21
22/// A clipboard write awaiting acknowledgement from the platform.
23pub struct ClipboardWrite {
24    text: String,
25    on_success: Box<dyn FnOnce() + Send + Sync>,
26}
27
28impl ClipboardWrite {
29    /// Returns the plain text to write.
30    pub fn text(&self) -> &str {
31        &self.text
32    }
33
34    /// Completes this write, applying its edit only after success.
35    pub fn complete(self, succeeded: bool) {
36        if succeeded {
37            (self.on_success)();
38        }
39    }
40}
41
42/// The focused editor's response to a clipboard action.
43pub enum ClipboardResponse {
44    /// No focused editor accepted the action.
45    Ignored,
46    /// The action was handled without a clipboard write.
47    Handled,
48    /// The platform must write text and acknowledge its result.
49    Write(ClipboardWrite),
50}
51
52/// Clipboard input routed to a node on the focused path.
53pub struct ClipboardInput<'a> {
54    /// The requested copy, cut, or paste action.
55    pub action: &'a ClipboardAction,
56    pub(crate) response: &'a mut Option<ClipboardResponse>,
57}
58
59impl ClipboardInput<'_> {
60    /// Consumes the action without requesting a platform write.
61    pub fn handle(&mut self) {
62        *self.response = Some(ClipboardResponse::Handled);
63    }
64
65    /// Requests a write and a callback after the platform confirms success.
66    pub fn write_text(&mut self, text: String, on_success: impl FnOnce() + Send + Sync + 'static) {
67        *self.response = Some(ClipboardResponse::Write(ClipboardWrite {
68            text,
69            on_success: Box::new(on_success),
70        }));
71    }
72}
73
74/// Node-local clipboard handling for focused editors.
75pub trait ClipboardInputModifierNode: Send + Sync + 'static {
76    /// Handles a clipboard action on the focused path.
77    fn on_clipboard_input(&self, input: ClipboardInput<'_>);
78}
79
80pub(crate) struct QueuedClipboardAction {
81    pub sequence: u64,
82    pub action: ClipboardAction,
83    pub reply: Box<dyn FnOnce(ClipboardResponse) + Send + Sync>,
84}
85
86const REQUESTS: crate::session::SessionLocal<RefCell<VecDeque<QueuedClipboardAction>>> =
87    crate::session::SessionLocal::new();
88
89/// Queues a user clipboard action in order with keyboard input.
90///
91/// Platform integrations supply paste text from the clipboard event and handle
92/// write responses using their platform clipboard service.
93/// Call this on the runtime's UI thread.
94pub fn request_clipboard_action(
95    action: ClipboardAction,
96    reply: impl FnOnce(ClipboardResponse) + Send + Sync + 'static,
97) {
98    REQUESTS.with_borrow_mut(|requests| {
99        requests.push_back(QueuedClipboardAction {
100            sequence: next_input_sequence(),
101            action,
102            reply: Box::new(reply),
103        })
104    });
105    schedule_runtime_redraw();
106}
107
108pub(crate) fn take_requests() -> Vec<QueuedClipboardAction> {
109    REQUESTS.with_borrow_mut(|requests| requests.drain(..).collect())
110}
111
112pub(crate) fn has_pending_requests() -> bool {
113    REQUESTS.with_borrow(|requests| !requests.is_empty())
114}
115
116pub(crate) fn clear_requests() {
117    let requests = REQUESTS.with_borrow_mut(std::mem::take);
118    drop(requests);
119}