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