gpui_component/
clipboard.rs

1use std::{cell::Cell, rc::Rc, time::Duration};
2
3use gpui::{
4    prelude::FluentBuilder, AnyElement, App, ClipboardItem, Element, ElementId, GlobalElementId,
5    IntoElement, LayoutId, ParentElement, SharedString, Styled, Window,
6};
7
8use crate::{
9    button::{Button, ButtonVariants as _},
10    h_flex, IconName, Sizable as _,
11};
12
13pub struct Clipboard {
14    id: ElementId,
15    value: SharedString,
16    value_fn: Option<Rc<dyn Fn(&mut Window, &mut App) -> SharedString>>,
17    content_builder: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyElement>>,
18    copied_callback: Option<Rc<dyn Fn(SharedString, &mut Window, &mut App)>>,
19}
20
21impl Clipboard {
22    pub fn new(id: impl Into<ElementId>) -> Self {
23        Self {
24            id: id.into(),
25            value: SharedString::default(),
26            value_fn: None,
27            content_builder: None,
28            copied_callback: None,
29        }
30    }
31
32    pub fn value(mut self, value: impl Into<SharedString>) -> Self {
33        self.value = value.into();
34        self
35    }
36
37    /// Set the value of the clipboard to the result of the given function. Default is None.
38    ///
39    /// When used this, the copy value will use the result of the function.
40    pub fn value_fn(
41        mut self,
42        value: impl Fn(&mut Window, &mut App) -> SharedString + 'static,
43    ) -> Self {
44        self.value_fn = Some(Rc::new(value));
45        self
46    }
47
48    pub fn on_copied<F>(mut self, handler: F) -> Self
49    where
50        F: Fn(SharedString, &mut Window, &mut App) + 'static,
51    {
52        self.copied_callback = Some(Rc::new(handler));
53        self
54    }
55
56    pub fn content<E, F>(mut self, builder: F) -> Self
57    where
58        E: IntoElement,
59        F: Fn(&mut Window, &mut App) -> E + 'static,
60    {
61        self.content_builder = Some(Box::new(move |window, cx| {
62            builder(window, cx).into_any_element()
63        }));
64        self
65    }
66}
67
68impl IntoElement for Clipboard {
69    type Element = Self;
70
71    fn into_element(self) -> Self::Element {
72        self
73    }
74}
75
76#[derive(Default)]
77pub struct ClipboardState {
78    copied: Cell<bool>,
79}
80
81impl Element for Clipboard {
82    type RequestLayoutState = AnyElement;
83
84    type PrepaintState = ();
85
86    fn id(&self) -> Option<ElementId> {
87        Some(self.id.clone())
88    }
89
90    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
91        None
92    }
93
94    fn request_layout(
95        &mut self,
96        global_id: Option<&GlobalElementId>,
97        _: Option<&gpui::InspectorElementId>,
98        window: &mut Window,
99        cx: &mut App,
100    ) -> (LayoutId, Self::RequestLayoutState) {
101        window.with_element_state::<ClipboardState, _>(global_id.unwrap(), |state, window| {
102            let state = state.unwrap_or_default();
103
104            let content_element = self
105                .content_builder
106                .as_ref()
107                .map(|builder| builder(window, cx).into_any_element());
108            let value = self.value.clone();
109            let clipboard_id = self.id.clone();
110            let copied_callback = self.copied_callback.as_ref().map(|c| c.clone());
111            let copied = state.copied.clone();
112            let copide_value = copied.get();
113            let value_fn = self.value_fn.clone();
114
115            let mut element = h_flex()
116                .gap_1()
117                .items_center()
118                .when_some(content_element, |this, element| this.child(element))
119                .child(
120                    Button::new(clipboard_id)
121                        .icon(if copide_value {
122                            IconName::Check
123                        } else {
124                            IconName::Copy
125                        })
126                        .ghost()
127                        .xsmall()
128                        .when(!copide_value, |this| {
129                            this.on_click(move |_, window, cx| {
130                                cx.stop_propagation();
131                                let value = value_fn
132                                    .as_ref()
133                                    .map(|f| f(window, cx))
134                                    .unwrap_or_else(|| value.clone());
135                                cx.write_to_clipboard(ClipboardItem::new_string(value.to_string()));
136                                copied.set(true);
137
138                                let copied = copied.clone();
139                                cx.spawn(async move |cx| {
140                                    cx.background_executor().timer(Duration::from_secs(2)).await;
141
142                                    copied.set(false);
143                                })
144                                .detach();
145
146                                if let Some(callback) = &copied_callback {
147                                    callback(value.clone(), window, cx);
148                                }
149                            })
150                        }),
151                )
152                .into_any_element();
153
154            ((element.request_layout(window, cx), element), state)
155        })
156    }
157
158    fn prepaint(
159        &mut self,
160        _: Option<&gpui::GlobalElementId>,
161        _: Option<&gpui::InspectorElementId>,
162        _: gpui::Bounds<gpui::Pixels>,
163        element: &mut Self::RequestLayoutState,
164        window: &mut Window,
165        cx: &mut App,
166    ) {
167        element.prepaint(window, cx);
168    }
169
170    fn paint(
171        &mut self,
172        _: Option<&gpui::GlobalElementId>,
173        _: Option<&gpui::InspectorElementId>,
174        _: gpui::Bounds<gpui::Pixels>,
175        element: &mut Self::RequestLayoutState,
176        _: &mut Self::PrepaintState,
177        window: &mut Window,
178        cx: &mut App,
179    ) {
180        element.paint(window, cx)
181    }
182}