clipboard.rs raw

//! The clipboard seam: the platform shell provides the real implementation
//! (window-system clipboards are a platform affair); widgets reach it
//! through the key- and pointer-dispatch paths.
//!
//! Reading is a *request*, not a call that returns text: on Wayland the
//! content comes from whatever client owns the selection, over a pipe that
//! client writes at its own pace — or never, if it is wedged. A platform
//! whose read must negotiate like that answers [`TextRequest::Pending`] and
//! delivers the text later through `WidgetTree::dispatch_paste`, off the UI
//! thread's critical path. A platform (or test double) that has the text in
//! hand answers [`TextRequest::Ready`] and the paste happens synchronously.

/// Which of the window system's selections to address.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Selection {
    /// The explicit copy/paste clipboard (ctrl-C / ctrl-V).
    Clipboard,
    /// The implicit selection: selecting text populates it, middle-click
    /// pastes it (the X11/Wayland "primary selection" convention).
    Primary,
}

/// The immediate answer to [`Clipboard::request_text`].
#[derive(Debug, PartialEq, Eq)]
pub enum TextRequest {
    /// The text (or its definite absence) was known on the spot.
    Ready(Option<String>),
    /// The platform is fetching. Whatever arrives — possibly nothing —
    /// comes back through `WidgetTree::dispatch_paste`, to whichever
    /// widget is focused when it does.
    Pending,
}

pub trait Clipboard {
    /// Begin reading `selection`'s text. Must not block on the selection's
    /// owner: answer [`TextRequest::Pending`] and deliver later instead.
    fn request_text(&mut self, selection: Selection) -> TextRequest;

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

/// A clipboard that holds nothing and accepts nothing: the default for
/// headless use.
#[derive(Default)]
pub struct NoClipboard;

impl Clipboard for NoClipboard {
    fn request_text(&mut self, _selection: Selection) -> TextRequest {
        TextRequest::Ready(None)
    }

    fn set_text(&mut self, _selection: Selection, _text: &str) {}
}

/// An in-process clipboard, for tests. Always answers immediately, so a
/// paste driven through it lands synchronously.
#[derive(Default)]
pub struct LocalClipboard {
    clipboard: Option<String>,
    primary: Option<String>,
}

impl LocalClipboard {
    fn slot(&mut self, selection: Selection) -> &mut Option<String> {
        match selection {
            Selection::Clipboard => &mut self.clipboard,
            Selection::Primary => &mut self.primary,
        }
    }

    /// What the clipboard holds, for test assertions.
    pub fn get_text(&mut self, selection: Selection) -> Option<String> {
        self.slot(selection).clone()
    }
}

impl Clipboard for LocalClipboard {
    fn request_text(&mut self, selection: Selection) -> TextRequest {
        TextRequest::Ready(self.slot(selection).clone())
    }

    fn set_text(&mut self, selection: Selection, text: &str) {
        *self.slot(selection) = Some(text.to_owned());
    }
}