@@ -1,11 +1,37 @@
+use std::time::{Duration, Instant};
+
use gpui::{
- AnyElement, App, ClipboardItem, IntoElement, ParentElement, RenderOnce, Styled, Window,
+ AnyElement, App, ClipboardItem, Context, ElementId, Entity, IntoElement, ParentElement,
+ RenderOnce, Styled, Window,
};
use crate::{Tooltip, prelude::*};
+const COPIED_STATE_DURATION: Duration = Duration::from_secs(2);
+
+struct CopyButtonState {
+ copied_at: Option<Instant>,
+}
+
+impl CopyButtonState {
+ fn new(_window: &mut Window, _cx: &mut Context<Self>) -> Self {
+ Self { copied_at: None }
+ }
+
+ fn is_copied(&self) -> bool {
+ self.copied_at
+ .map(|t| t.elapsed() < COPIED_STATE_DURATION)
+ .unwrap_or(false)
+ }
+
+ fn mark_copied(&mut self) {
+ self.copied_at = Some(Instant::now());
+ }
+}
+
#[derive(IntoElement, RegisterComponent)]
pub struct CopyButton {
+ id: ElementId,
message: SharedString,
icon_size: IconSize,
disabled: bool,
@@ -15,8 +41,9 @@ pub struct CopyButton {
}
impl CopyButton {
- pub fn new(message: impl Into<SharedString>) -> Self {
+ pub fn new(id: impl Into<ElementId>, message: impl Into<SharedString>) -> Self {
Self {
+ id: id.into(),
message: message.into(),
icon_size: IconSize::Small,
disabled: false,
@@ -56,38 +83,47 @@ impl CopyButton {
}
impl RenderOnce for CopyButton {
- fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
+ fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
+ let id = self.id.clone();
let message = self.message;
- let message_clone = message.clone();
-
- let id = format!("copy-button-{}", message_clone);
+ let custom_on_click = self.custom_on_click;
+ let visible_on_hover = self.visible_on_hover;
- let copied = cx
- .read_from_clipboard()
- .map(|item| item.text().as_ref() == Some(&message_clone.into()))
- .unwrap_or(false);
+ let state: Entity<CopyButtonState> =
+ window.use_keyed_state(id.clone(), cx, CopyButtonState::new);
+ let is_copied = state.read(cx).is_copied();
- let (icon, color, tooltip) = if copied {
+ let (icon, color, tooltip) = if is_copied {
(IconName::Check, Color::Success, "Copied!".into())
} else {
(IconName::Copy, Color::Muted, self.tooltip_label)
};
- let custom_on_click = self.custom_on_click;
- let visible_on_hover = self.visible_on_hover;
-
let button = IconButton::new(id, icon)
.icon_color(color)
.icon_size(self.icon_size)
.disabled(self.disabled)
.tooltip(Tooltip::text(tooltip))
.on_click(move |_, window, cx| {
+ state.update(cx, |state, _cx| {
+ state.mark_copied();
+ });
+
if let Some(custom_on_click) = custom_on_click.as_ref() {
(custom_on_click)(window, cx);
} else {
cx.stop_propagation();
- cx.write_to_clipboard(ClipboardItem::new_string(message.clone().into()));
+ cx.write_to_clipboard(ClipboardItem::new_string(message.to_string()));
}
+
+ let state_id = state.entity_id();
+ cx.spawn(async move |cx| {
+ cx.background_executor().timer(COPIED_STATE_DURATION).await;
+ cx.update(|cx| {
+ cx.notify(state_id);
+ })
+ })
+ .detach();
});
if let Some(visible_on_hover) = visible_on_hover {
@@ -109,23 +145,14 @@ impl Component for CopyButton {
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
let label_text = "Here's an example label";
- let mut counter: usize = 0;
-
- let mut copy_b = || {
- counter += 1;
- CopyButton::new(format!(
- "Here's an example label (id for uniqueness: {} — ignore this)",
- counter
- ))
- };
- let example = vec![
+ let examples = vec![
single_example(
"Default",
h_flex()
.gap_1()
.child(Label::new(label_text).size(LabelSize::Small))
- .child(copy_b())
+ .child(CopyButton::new("preview-default", label_text))
.into_any_element(),
),
single_example(
@@ -133,9 +160,15 @@ impl Component for CopyButton {
h_flex()
.gap_1()
.child(Label::new(label_text).size(LabelSize::Small))
- .child(copy_b().icon_size(IconSize::XSmall))
- .child(copy_b().icon_size(IconSize::Medium))
- .child(copy_b().icon_size(IconSize::XLarge))
+ .child(
+ CopyButton::new("preview-xsmall", label_text).icon_size(IconSize::XSmall),
+ )
+ .child(
+ CopyButton::new("preview-medium", label_text).icon_size(IconSize::Medium),
+ )
+ .child(
+ CopyButton::new("preview-xlarge", label_text).icon_size(IconSize::XLarge),
+ )
.into_any_element(),
),
single_example(
@@ -143,7 +176,10 @@ impl Component for CopyButton {
h_flex()
.gap_1()
.child(Label::new(label_text).size(LabelSize::Small))
- .child(copy_b().tooltip_label("Custom tooltip label"))
+ .child(
+ CopyButton::new("preview-tooltip", label_text)
+ .tooltip_label("Custom tooltip label"),
+ )
.into_any_element(),
),
single_example(
@@ -152,11 +188,13 @@ impl Component for CopyButton {
.group("container")
.gap_1()
.child(Label::new(label_text).size(LabelSize::Small))
- .child(copy_b().visible_on_hover("container"))
+ .child(
+ CopyButton::new("preview-hover", label_text).visible_on_hover("container"),
+ )
.into_any_element(),
),
];
- Some(example_group(example).vertical().into_any_element())
+ Some(example_group(examples).vertical().into_any_element())
}
}