copy_button.rs

  1use gpui::{
  2    AnyElement, App, ClipboardItem, IntoElement, ParentElement, RenderOnce, Styled, Window,
  3};
  4
  5use crate::{Tooltip, prelude::*};
  6
  7#[derive(IntoElement, RegisterComponent)]
  8pub struct CopyButton {
  9    message: SharedString,
 10    icon_size: IconSize,
 11    disabled: bool,
 12    tooltip_label: SharedString,
 13    visible_on_hover: Option<SharedString>,
 14    custom_on_click: Option<Box<dyn Fn(&mut Window, &mut App) + 'static>>,
 15}
 16
 17impl CopyButton {
 18    pub fn new(message: impl Into<SharedString>) -> Self {
 19        Self {
 20            message: message.into(),
 21            icon_size: IconSize::Small,
 22            disabled: false,
 23            tooltip_label: "Copy".into(),
 24            visible_on_hover: None,
 25            custom_on_click: None,
 26        }
 27    }
 28
 29    pub fn icon_size(mut self, icon_size: IconSize) -> Self {
 30        self.icon_size = icon_size;
 31        self
 32    }
 33
 34    pub fn disabled(mut self, disabled: bool) -> Self {
 35        self.disabled = disabled;
 36        self
 37    }
 38
 39    pub fn tooltip_label(mut self, tooltip_label: impl Into<SharedString>) -> Self {
 40        self.tooltip_label = tooltip_label.into();
 41        self
 42    }
 43
 44    pub fn visible_on_hover(mut self, visible_on_hover: impl Into<SharedString>) -> Self {
 45        self.visible_on_hover = Some(visible_on_hover.into());
 46        self
 47    }
 48
 49    pub fn custom_on_click(
 50        mut self,
 51        custom_on_click: impl Fn(&mut Window, &mut App) + 'static,
 52    ) -> Self {
 53        self.custom_on_click = Some(Box::new(custom_on_click));
 54        self
 55    }
 56}
 57
 58impl RenderOnce for CopyButton {
 59    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
 60        let message = self.message;
 61        let message_clone = message.clone();
 62
 63        let id = format!("copy-button-{}", message_clone);
 64
 65        let copied = cx
 66            .read_from_clipboard()
 67            .map(|item| item.text().as_ref() == Some(&message_clone.into()))
 68            .unwrap_or(false);
 69
 70        let (icon, color, tooltip) = if copied {
 71            (IconName::Check, Color::Success, "Copied!".into())
 72        } else {
 73            (IconName::Copy, Color::Muted, self.tooltip_label)
 74        };
 75
 76        let custom_on_click = self.custom_on_click;
 77        let visible_on_hover = self.visible_on_hover;
 78
 79        let button = IconButton::new(id, icon)
 80            .icon_color(color)
 81            .icon_size(self.icon_size)
 82            .disabled(self.disabled)
 83            .tooltip(Tooltip::text(tooltip))
 84            .on_click(move |_, window, cx| {
 85                if let Some(custom_on_click) = custom_on_click.as_ref() {
 86                    (custom_on_click)(window, cx);
 87                } else {
 88                    cx.stop_propagation();
 89                    cx.write_to_clipboard(ClipboardItem::new_string(message.clone().into()));
 90                }
 91            });
 92
 93        if let Some(visible_on_hover) = visible_on_hover {
 94            button.visible_on_hover(visible_on_hover)
 95        } else {
 96            button
 97        }
 98    }
 99}
100
101impl Component for CopyButton {
102    fn scope() -> ComponentScope {
103        ComponentScope::Input
104    }
105
106    fn description() -> Option<&'static str> {
107        Some("An icon button that encapsulates the logic to copy a string into the clipboard.")
108    }
109
110    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
111        let label_text = "Here's an example label";
112        let mut counter: usize = 0;
113
114        let mut copy_b = || {
115            counter += 1;
116            CopyButton::new(format!(
117                "Here's an example label (id for uniqueness: {} — ignore this)",
118                counter
119            ))
120        };
121
122        let example = vec![
123            single_example(
124                "Default",
125                h_flex()
126                    .gap_1()
127                    .child(Label::new(label_text).size(LabelSize::Small))
128                    .child(copy_b())
129                    .into_any_element(),
130            ),
131            single_example(
132                "Multiple Icon Sizes",
133                h_flex()
134                    .gap_1()
135                    .child(Label::new(label_text).size(LabelSize::Small))
136                    .child(copy_b().icon_size(IconSize::XSmall))
137                    .child(copy_b().icon_size(IconSize::Medium))
138                    .child(copy_b().icon_size(IconSize::XLarge))
139                    .into_any_element(),
140            ),
141            single_example(
142                "Custom Tooltip Label",
143                h_flex()
144                    .gap_1()
145                    .child(Label::new(label_text).size(LabelSize::Small))
146                    .child(copy_b().tooltip_label("Custom tooltip label"))
147                    .into_any_element(),
148            ),
149            single_example(
150                "Visible On Hover",
151                h_flex()
152                    .group("container")
153                    .gap_1()
154                    .child(Label::new(label_text).size(LabelSize::Small))
155                    .child(copy_b().visible_on_hover("container"))
156                    .into_any_element(),
157            ),
158        ];
159
160        Some(example_group(example).vertical().into_any_element())
161    }
162}