gpui_component/
clipboard.rs

1use std::{rc::Rc, time::Duration};
2
3use gpui::{
4    prelude::FluentBuilder, App, ClipboardItem, ElementId, IntoElement, RenderOnce, SharedString,
5    Window,
6};
7
8use crate::{
9    button::{Button, ButtonVariants as _},
10    IconName, Sizable as _,
11};
12
13/// An element that provides clipboard copy functionality.
14#[derive(IntoElement)]
15pub struct Clipboard {
16    id: ElementId,
17    value: SharedString,
18    value_fn: Option<Rc<dyn Fn(&mut Window, &mut App) -> SharedString>>,
19    on_copied: Option<Rc<dyn Fn(SharedString, &mut Window, &mut App)>>,
20}
21
22impl Clipboard {
23    /// Create a new Clipboard element with the given ID.
24    pub fn new(id: impl Into<ElementId>) -> Self {
25        Self {
26            id: id.into(),
27            value: SharedString::default(),
28            value_fn: None,
29            on_copied: None,
30        }
31    }
32
33    /// Set the value for copying to the clipboard. Default is an empty string.
34    pub fn value(mut self, value: impl Into<SharedString>) -> Self {
35        self.value = value.into();
36        self
37    }
38
39    /// Set the value of the clipboard to the result of the given function. Default is None.
40    ///
41    /// When used this, the copy value will use the result of the function.
42    pub fn value_fn(
43        mut self,
44        value: impl Fn(&mut Window, &mut App) -> SharedString + 'static,
45    ) -> Self {
46        self.value_fn = Some(Rc::new(value));
47        self
48    }
49
50    /// Set a callback to be invoked when the content is copied to the clipboard.
51    pub fn on_copied<F>(mut self, handler: F) -> Self
52    where
53        F: Fn(SharedString, &mut Window, &mut App) + 'static,
54    {
55        self.on_copied = Some(Rc::new(handler));
56        self
57    }
58}
59
60impl RenderOnce for Clipboard {
61    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
62        let state = window.use_keyed_state(self.id.clone(), cx, |_, _| ClipboardState::default());
63
64        let value = self.value.clone();
65        let clipboard_id = self.id.clone();
66        let copied = state.read(cx).copied;
67        let value_fn = self.value_fn.clone();
68
69        Button::new(clipboard_id)
70            .icon(if copied {
71                IconName::Check
72            } else {
73                IconName::Copy
74            })
75            .ghost()
76            .xsmall()
77            .when(!copied, |this| {
78                this.on_click({
79                    let state = state.clone();
80                    let on_copied = self.on_copied.clone();
81                    move |_, window, cx| {
82                        cx.stop_propagation();
83                        let value = value_fn
84                            .as_ref()
85                            .map(|f| f(window, cx))
86                            .unwrap_or_else(|| value.clone());
87                        cx.write_to_clipboard(ClipboardItem::new_string(value.to_string()));
88                        state.update(cx, |state, cx| {
89                            state.copied = true;
90                            cx.notify();
91                        });
92
93                        let state = state.clone();
94                        cx.spawn(async move |cx| {
95                            cx.background_executor().timer(Duration::from_secs(2)).await;
96                            _ = state.update(cx, |state, cx| {
97                                state.copied = false;
98                                cx.notify();
99                            });
100                        })
101                        .detach();
102
103                        if let Some(on_copied) = &on_copied {
104                            on_copied(value.clone(), window, cx);
105                        }
106                    }
107                })
108            })
109    }
110}
111
112#[doc(hidden)]
113#[derive(Default)]
114struct ClipboardState {
115    copied: bool,
116}