Skip to main content

guise/
copybutton.rs

1//! `CopyButton` — copies text to the clipboard, with transient feedback.
2
3use std::time::Duration;
4
5use gpui::prelude::*;
6use gpui::{div, px, ClickEvent, ClipboardItem, Context, IntoElement, SharedString, Window};
7
8use crate::devtools::Probed;
9use crate::theme::{theme, ColorName, Size};
10
11/// A small button that writes `text` to the system clipboard when clicked, then
12/// shows a "Copied" state for a moment. A gpui entity — create with
13/// `cx.new(|_| CopyButton::new("…"))`.
14pub struct CopyButton {
15    text: SharedString,
16    label: SharedString,
17    copied_label: SharedString,
18    copied: bool,
19}
20
21impl CopyButton {
22    pub fn new(text: impl Into<SharedString>) -> Self {
23        CopyButton {
24            text: text.into(),
25            label: SharedString::new_static("Copy"),
26            copied_label: SharedString::new_static("\u{2713} Copied"),
27            copied: false,
28        }
29    }
30
31    /// Override the idle label (default "Copy").
32    pub fn label(mut self, label: impl Into<SharedString>) -> Self {
33        self.label = label.into();
34        self
35    }
36
37    /// The text this button copies.
38    pub fn text(&self) -> SharedString {
39        self.text.clone()
40    }
41
42    /// Replace the text to copy.
43    pub fn set_text(&mut self, text: impl Into<SharedString>) {
44        self.text = text.into();
45    }
46
47    fn copy(&mut self, _: &ClickEvent, _: &mut Window, cx: &mut Context<Self>) {
48        cx.write_to_clipboard(ClipboardItem::new_string(self.text.to_string()));
49        self.copied = true;
50        cx.notify();
51
52        // Revert the "Copied" state after a beat.
53        cx.spawn(async move |this, cx| {
54            cx.background_executor()
55                .timer(Duration::from_millis(1200))
56                .await;
57            this.update(cx, |this, cx| {
58                this.copied = false;
59                cx.notify();
60            })
61            .ok();
62        })
63        .detach();
64    }
65}
66
67impl Render for CopyButton {
68    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
69        let t = theme(cx);
70        let dark = t.scheme.is_dark();
71        let copied = self.copied;
72
73        let (bg, fg) = if copied {
74            (
75                t.color(ColorName::Teal, if dark { 8 } else { 0 }).hsla(),
76                t.color(ColorName::Teal, if dark { 3 } else { 7 }).hsla(),
77            )
78        } else {
79            (t.surface_hover().hsla(), t.dimmed().hsla())
80        };
81        let hover_bg = t.color(ColorName::Gray, if dark { 6 } else { 2 }).hsla();
82        let label = if copied {
83            self.copied_label.clone()
84        } else {
85            self.label.clone()
86        };
87
88        let mut el = div()
89            .id("guise-copy-button")
90            .flex()
91            .items_center()
92            .h(px(24.0))
93            .px(px(8.0))
94            .rounded(px(t.radius(Size::Sm)))
95            .bg(bg)
96            .text_color(fg)
97            .text_size(px(12.0))
98            .child(label)
99            .on_click(cx.listener(Self::copy));
100        if !copied {
101            el = el.hover(move |s| s.bg(hover_bg));
102        }
103        el.probe("CopyButton")
104    }
105}