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