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
58        .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.probe("CopyButton")
105  }
106}