mirror of
https://github.com/zed-industries/zed.git
synced 2026-07-09 16:00:35 +00:00
ui: Improve performance in the CopyButton component (#47292)
This PR improves the performance of the `CopyButton` component by tracking the copied state locally through a `CopyButtonState` struct instead of making an OS call every time the component re-renders. Also pushing a slight improvement here by resetting the state after two seconds so as to make the check mark go away after you clicked to copy. Release Notes: - N/A
This commit is contained in:
parent
099650fdfb
commit
e1e7676b5a
11 changed files with 89 additions and 44 deletions
|
|
@ -571,7 +571,7 @@ impl Render for AcpToolsToolbarItemView {
|
|||
.serialize_observed_messages()
|
||||
.unwrap_or_default();
|
||||
|
||||
CopyButton::new(message)
|
||||
CopyButton::new("copy-all-messages", message)
|
||||
.tooltip_label("Copy All Messages")
|
||||
.disabled(!has_messages)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -4357,7 +4357,7 @@ impl AcpThreadView {
|
|||
}))
|
||||
.child(
|
||||
div().absolute().top_1().right_1().child(
|
||||
CopyButton::new(command_source.to_string())
|
||||
CopyButton::new("copy-command", command_source.to_string())
|
||||
.tooltip_label("Copy Command")
|
||||
.visible_on_hover(command_group),
|
||||
),
|
||||
|
|
@ -7794,7 +7794,7 @@ impl AcpThreadView {
|
|||
fn create_copy_button(&self, message: impl Into<String>) -> impl IntoElement {
|
||||
let message = message.into();
|
||||
|
||||
CopyButton::new(message).tooltip_label("Copy Error Message")
|
||||
CopyButton::new("copy-error-message", message).tooltip_label("Copy Error Message")
|
||||
}
|
||||
|
||||
fn dismiss_error_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
|
|
|
|||
|
|
@ -2541,7 +2541,7 @@ impl CollabPanel {
|
|||
|
||||
let button = match section {
|
||||
Section::ActiveCall => channel_link.map(|channel_link| {
|
||||
CopyButton::new(channel_link)
|
||||
CopyButton::new("copy-channel-link", channel_link)
|
||||
.visible_on_hover("section-header")
|
||||
.tooltip_label("Copy Channel Link")
|
||||
.into_any_element()
|
||||
|
|
|
|||
|
|
@ -1027,7 +1027,7 @@ impl DiagnosticPopover {
|
|||
)
|
||||
.child(div().absolute().top_1().right_1().child({
|
||||
let message = self.local_diagnostic.diagnostic.message.clone();
|
||||
CopyButton::new(message).tooltip_label("Copy Diagnostic")
|
||||
CopyButton::new("copy-diagnostic", message).tooltip_label("Copy Diagnostic")
|
||||
}))
|
||||
.custom_scrollbars(
|
||||
Scrollbars::for_settings::<EditorSettings>()
|
||||
|
|
|
|||
|
|
@ -337,7 +337,7 @@ impl BlameRenderer for GitBlameRenderer {
|
|||
)
|
||||
.child(Divider::vertical())
|
||||
.child(
|
||||
CopyButton::new(sha.to_string())
|
||||
CopyButton::new("copy-blame-sha", sha.to_string())
|
||||
.tooltip_label("Copy SHA"),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -343,7 +343,10 @@ impl Render for CommitTooltip {
|
|||
),
|
||||
)
|
||||
.child(Divider::vertical())
|
||||
.child(CopyButton::new(full_sha).tooltip_label("Copy SHA")),
|
||||
.child(
|
||||
CopyButton::new("copy-commit-sha", full_sha)
|
||||
.tooltip_label("Copy SHA"),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1451,7 +1451,7 @@ fn render_copy_code_block_button(
|
|||
) -> impl IntoElement {
|
||||
let id = ElementId::named_usize("copy-markdown-code", id);
|
||||
|
||||
CopyButton::new(code.clone()).custom_on_click({
|
||||
CopyButton::new(id.clone(), code.clone()).custom_on_click({
|
||||
let markdown = markdown;
|
||||
move |_window, cx| {
|
||||
let id = id.clone();
|
||||
|
|
|
|||
|
|
@ -623,7 +623,7 @@ fn render_markdown_code_block(
|
|||
StyledText::new(parsed.contents.clone())
|
||||
};
|
||||
|
||||
let copy_block_button = CopyButton::new(parsed.contents.clone())
|
||||
let copy_block_button = CopyButton::new("copy-codeblock", parsed.contents.clone())
|
||||
.tooltip_label("Copy Codeblock")
|
||||
.visible_on_hover("markdown-block");
|
||||
|
||||
|
|
|
|||
|
|
@ -246,7 +246,8 @@ impl Output {
|
|||
let traceback_text = traceback.read(cx).full_text();
|
||||
let full_error = format!("{}: {}\n{}", ename, evalue, traceback_text);
|
||||
|
||||
CopyButton::new(full_error).tooltip_label("Copy Full Error")
|
||||
CopyButton::new("copy-full-error", full_error)
|
||||
.tooltip_label("Copy Full Error")
|
||||
})
|
||||
.child(
|
||||
IconButton::new(
|
||||
|
|
|
|||
|
|
@ -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 custom_on_click = self.custom_on_click;
|
||||
let visible_on_hover = self.visible_on_hover;
|
||||
|
||||
let id = format!("copy-button-{}", message_clone);
|
||||
let state: Entity<CopyButtonState> =
|
||||
window.use_keyed_state(id.clone(), cx, CopyButtonState::new);
|
||||
let is_copied = state.read(cx).is_copied();
|
||||
|
||||
let copied = cx
|
||||
.read_from_clipboard()
|
||||
.map(|item| item.text().as_ref() == Some(&message_clone.into()))
|
||||
.unwrap_or(false);
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -308,8 +308,11 @@ impl Render for LanguageServerPrompt {
|
|||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
CopyButton::new(request.message.clone())
|
||||
.tooltip_label("Copy Description"),
|
||||
CopyButton::new(
|
||||
"copy-description",
|
||||
request.message.clone(),
|
||||
)
|
||||
.tooltip_label("Copy Description"),
|
||||
)
|
||||
.child(
|
||||
IconButton::new(close_id, close_icon)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue