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 tooltip_show_delay: Option<Duration>,
23 accessibility_label: Option<SharedString>,
24 size: Size,
25}
26
27impl Sizable for Clipboard {
28 fn with_size(mut self, size: impl Into<Size>) -> Self {
29 self.size = size.into();
30 self
31 }
32}
33
34impl Clipboard {
35 pub fn new(id: impl Into<ElementId>) -> Self {
37 Self {
38 id: id.into(),
39 value: SharedString::default(),
40 value_fn: None,
41 on_copied: None,
42 tooltip_text: None,
43 tooltip_show_delay: None,
44 accessibility_label: None,
45 size: Size::XSmall,
46 }
47 }
48
49 pub fn tooltip(mut self, tooltip: impl Into<SharedString>) -> Self {
51 self.tooltip_text = Some(tooltip.into());
52 self
53 }
54
55 pub fn tooltip_show_delay(mut self, delay: Duration) -> Self {
59 self.tooltip_show_delay = Some(delay);
60 self
61 }
62
63 pub fn accessibility_label(mut self, label: impl Into<SharedString>) -> Self {
68 self.accessibility_label = Some(label.into());
69 self
70 }
71
72 pub fn value(mut self, value: impl Into<SharedString>) -> Self {
74 self.value = value.into();
75 self
76 }
77
78 pub fn value_fn(
82 mut self,
83 value: impl Fn(&mut Window, &mut App) -> SharedString + 'static,
84 ) -> Self {
85 self.value_fn = Some(Rc::new(value));
86 self
87 }
88
89 pub fn on_copied<F>(mut self, handler: F) -> Self
91 where
92 F: Fn(SharedString, &mut Window, &mut App) + 'static,
93 {
94 self.on_copied = Some(Rc::new(handler));
95 self
96 }
97}
98
99impl RenderOnce for Clipboard {
100 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
101 let state = window.use_keyed_state(self.id.clone(), cx, |_, _| ClipboardState::default());
102
103 let value = self.value.clone();
104 let clipboard_id = self.id.clone();
105 let copied = state.read(cx).copied;
106 let value_fn = self.value_fn.clone();
107
108 Button::new(clipboard_id)
109 .icon(if copied {
110 IconName::Check
111 } else {
112 IconName::Copy
113 })
114 .ghost()
115 .with_size(self.size)
116 .when_some(self.tooltip_text, |this, text| this.tooltip(text))
117 .when_some(self.tooltip_show_delay, |this, delay| {
118 this.tooltip_show_delay(delay)
119 })
120 .accessibility_label(
121 self.accessibility_label
122 .unwrap_or_else(|| t!("Copy").into()),
123 )
124 .when(!copied, |this| {
125 this.on_click({
126 let state = state.clone();
127 let on_copied = self.on_copied.clone();
128 move |_, window, cx| {
129 cx.stop_propagation();
130 let value = value_fn
131 .as_ref()
132 .map(|f| f(window, cx))
133 .unwrap_or_else(|| value.clone());
134 cx.write_to_clipboard(ClipboardItem::new_string(value.to_string()));
135 state.update(cx, |state, cx| {
136 state.copied = true;
137 cx.notify();
138 });
139
140 let state = state.clone();
141 cx.spawn(async move |cx| {
142 cx.background_executor().timer(Duration::from_secs(2)).await;
143 _ = state.update(cx, |state, cx| {
144 state.copied = false;
145 cx.notify();
146 });
147 })
148 .detach();
149
150 if let Some(on_copied) = &on_copied {
151 on_copied(value.clone(), window, cx);
152 }
153 }
154 })
155 })
156 }
157}
158
159#[doc(hidden)]
160#[derive(Default)]
161struct ClipboardState {
162 copied: bool,
163}