gpui_component/
clipboard.rs1use std::{rc::Rc, time::Duration};
2
3use gpui::{
4 App, ClipboardItem, ElementId, IntoElement, RenderOnce, SharedString, Window,
5 prelude::FluentBuilder,
6};
7use rust_i18n::t;
8
9use crate::{
10 IconName, Sizable, Size,
11 button::{Button, ButtonVariants as _},
12};
13
14#[derive(IntoElement)]
16pub struct Clipboard {
17 id: ElementId,
18 value: SharedString,
19 value_fn: Option<Rc<dyn Fn(&mut Window, &mut App) -> SharedString>>,
20 on_copied: Option<Rc<dyn Fn(SharedString, &mut Window, &mut App)>>,
21 tooltip_text: Option<SharedString>,
22 accessibility_label: Option<SharedString>,
23 size: Size,
24}
25
26impl Sizable for Clipboard {
27 fn with_size(mut self, size: impl Into<Size>) -> Self {
28 self.size = size.into();
29 self
30 }
31}
32
33impl Clipboard {
34 pub fn new(id: impl Into<ElementId>) -> Self {
36 Self {
37 id: id.into(),
38 value: SharedString::default(),
39 value_fn: None,
40 on_copied: None,
41 tooltip_text: None,
42 accessibility_label: None,
43 size: Size::XSmall,
44 }
45 }
46
47 pub fn tooltip(mut self, tooltip: impl Into<SharedString>) -> Self {
49 self.tooltip_text = Some(tooltip.into());
50 self
51 }
52
53 pub fn accessibility_label(mut self, label: impl Into<SharedString>) -> Self {
58 self.accessibility_label = Some(label.into());
59 self
60 }
61
62 pub fn value(mut self, value: impl Into<SharedString>) -> Self {
64 self.value = value.into();
65 self
66 }
67
68 pub fn value_fn(
72 mut self,
73 value: impl Fn(&mut Window, &mut App) -> SharedString + 'static,
74 ) -> Self {
75 self.value_fn = Some(Rc::new(value));
76 self
77 }
78
79 pub fn on_copied<F>(mut self, handler: F) -> Self
81 where
82 F: Fn(SharedString, &mut Window, &mut App) + 'static,
83 {
84 self.on_copied = Some(Rc::new(handler));
85 self
86 }
87}
88
89impl RenderOnce for Clipboard {
90 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
91 let state = window.use_keyed_state(self.id.clone(), cx, |_, _| ClipboardState::default());
92
93 let value = self.value.clone();
94 let clipboard_id = self.id.clone();
95 let copied = state.read(cx).copied;
96 let value_fn = self.value_fn.clone();
97
98 Button::new(clipboard_id)
99 .icon(if copied {
100 IconName::Check
101 } else {
102 IconName::Copy
103 })
104 .ghost()
105 .with_size(self.size)
106 .when_some(self.tooltip_text, |this, text| this.tooltip(text))
107 .accessibility_label(
108 self.accessibility_label
109 .unwrap_or_else(|| t!("Copy").into()),
110 )
111 .when(!copied, |this| {
112 this.on_click({
113 let state = state.clone();
114 let on_copied = self.on_copied.clone();
115 move |_, window, cx| {
116 cx.stop_propagation();
117 let value = value_fn
118 .as_ref()
119 .map(|f| f(window, cx))
120 .unwrap_or_else(|| value.clone());
121 cx.write_to_clipboard(ClipboardItem::new_string(value.to_string()));
122 state.update(cx, |state, cx| {
123 state.copied = true;
124 cx.notify();
125 });
126
127 let state = state.clone();
128 cx.spawn(async move |cx| {
129 cx.background_executor().timer(Duration::from_secs(2)).await;
130 _ = state.update(cx, |state, cx| {
131 state.copied = false;
132 cx.notify();
133 });
134 })
135 .detach();
136
137 if let Some(on_copied) = &on_copied {
138 on_copied(value.clone(), window, cx);
139 }
140 }
141 })
142 })
143 }
144}
145
146#[doc(hidden)]
147#[derive(Default)]
148struct ClipboardState {
149 copied: bool,
150}