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