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 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}