Skip to main content

gpui_component/
clipboard.rs

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