From 20930469428ce5664ef2b1f8566319256911f6de Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Wed, 5 Nov 2025 15:17:58 +0800 Subject: [PATCH] clipboard: Removed `content` method from Clipboard. (#1520) ## Break Change - The `content` method has been removed from `Clipboard`, if you want display something, you can wrap it in a h_flex. --- crates/story/src/clipboard_story.rs | 30 ++++++---- crates/ui/src/clipboard.rs | 90 ++++++++++++----------------- docs/docs/components/clipboard.md | 46 +++++++-------- 3 files changed, 77 insertions(+), 89 deletions(-) diff --git a/crates/story/src/clipboard_story.rs b/crates/story/src/clipboard_story.rs index e8df24a3..dbd0caa4 100644 --- a/crates/story/src/clipboard_story.rs +++ b/crates/story/src/clipboard_story.rs @@ -6,6 +6,7 @@ use gpui::{ use gpui_component::{ ContextModal, clipboard::Clipboard, + h_flex, input::{Input, InputState}, label::Label, v_flex, @@ -60,17 +61,24 @@ impl Render for ClipboardStory { .gap_6() .child( section("Clipboard").max_w_md().child( - Clipboard::new("clipboard1") - .content(|_, _| Label::new("A clipboard button")) - .value_fn({ - let view = cx.entity().clone(); - move |_, cx| { - SharedString::from(format!("masked :{}", view.read(cx).masked)) - } - }) - .on_copied(|value, window, cx| { - window.push_notification(format!("Copied value: {}", value), cx) - }), + h_flex() + .gap_2() + .child(Label::new("A clipboard button")) + .child( + Clipboard::new("clipboard1") + .value_fn({ + let view = cx.entity().clone(); + move |_, cx| { + SharedString::from(format!( + "masked :{}", + view.read(cx).masked + )) + } + }) + .on_copied(|value, window, cx| { + window.push_notification(format!("Copied value: {}", value), cx) + }), + ), ), ) .child( diff --git a/crates/ui/src/clipboard.rs b/crates/ui/src/clipboard.rs index 1f5d44ff..7c446fba 100644 --- a/crates/ui/src/clipboard.rs +++ b/crates/ui/src/clipboard.rs @@ -2,33 +2,34 @@ use std::{cell::Cell, rc::Rc, time::Duration}; use gpui::{ prelude::FluentBuilder, AnyElement, App, ClipboardItem, Element, ElementId, GlobalElementId, - IntoElement, LayoutId, ParentElement, SharedString, Styled, Window, + IntoElement, LayoutId, SharedString, Window, }; use crate::{ button::{Button, ButtonVariants as _}, - h_flex, IconName, Sizable as _, + IconName, Sizable as _, }; +/// An element that provides clipboard copy functionality. pub struct Clipboard { id: ElementId, value: SharedString, value_fn: Option SharedString>>, - content_builder: Option AnyElement>>, copied_callback: Option>, } impl Clipboard { + /// Create a new Clipboard element with the given ID. pub fn new(id: impl Into) -> Self { Self { id: id.into(), value: SharedString::default(), value_fn: None, - content_builder: None, copied_callback: None, } } + /// Set the value for copying to the clipboard. Default is an empty string. pub fn value(mut self, value: impl Into) -> Self { self.value = value.into(); self @@ -45,6 +46,7 @@ impl Clipboard { self } + /// Set a callback to be invoked when the content is copied to the clipboard. pub fn on_copied(mut self, handler: F) -> Self where F: Fn(SharedString, &mut Window, &mut App) + 'static, @@ -52,17 +54,6 @@ impl Clipboard { self.copied_callback = Some(Rc::new(handler)); self } - - pub fn content(mut self, builder: F) -> Self - where - E: IntoElement, - F: Fn(&mut Window, &mut App) -> E + 'static, - { - self.content_builder = Some(Box::new(move |window, cx| { - builder(window, cx).into_any_element() - })); - self - } } impl IntoElement for Clipboard { @@ -73,6 +64,7 @@ impl IntoElement for Clipboard { } } +#[doc(hidden)] #[derive(Default)] pub struct ClipboardState { copied: Cell, @@ -101,10 +93,6 @@ impl Element for Clipboard { window.with_element_state::(global_id.unwrap(), |state, window| { let state = state.unwrap_or_default(); - let content_element = self - .content_builder - .as_ref() - .map(|builder| builder(window, cx).into_any_element()); let value = self.value.clone(); let clipboard_id = self.id.clone(); let copied_callback = self.copied_callback.as_ref().map(|c| c.clone()); @@ -112,43 +100,37 @@ impl Element for Clipboard { let copide_value = copied.get(); let value_fn = self.value_fn.clone(); - let mut element = h_flex() - .gap_1() - .items_center() - .when_some(content_element, |this, element| this.child(element)) - .child( - Button::new(clipboard_id) - .icon(if copide_value { - IconName::Check - } else { - IconName::Copy + let mut element = Button::new(clipboard_id) + .icon(if copide_value { + IconName::Check + } else { + IconName::Copy + }) + .ghost() + .xsmall() + .when(!copide_value, |this| { + this.on_click(move |_, window, cx| { + cx.stop_propagation(); + let value = value_fn + .as_ref() + .map(|f| f(window, cx)) + .unwrap_or_else(|| value.clone()); + cx.write_to_clipboard(ClipboardItem::new_string(value.to_string())); + copied.set(true); + + let copied = copied.clone(); + cx.spawn(async move |cx| { + cx.background_executor().timer(Duration::from_secs(2)).await; + + copied.set(false); }) - .ghost() - .xsmall() - .when(!copide_value, |this| { - this.on_click(move |_, window, cx| { - cx.stop_propagation(); - let value = value_fn - .as_ref() - .map(|f| f(window, cx)) - .unwrap_or_else(|| value.clone()); - cx.write_to_clipboard(ClipboardItem::new_string(value.to_string())); - copied.set(true); + .detach(); - let copied = copied.clone(); - cx.spawn(async move |cx| { - cx.background_executor().timer(Duration::from_secs(2)).await; - - copied.set(false); - }) - .detach(); - - if let Some(callback) = &copied_callback { - callback(value.clone(), window, cx); - } - }) - }), - ) + if let Some(callback) = &copied_callback { + callback(value.clone(), window, cx); + } + }) + }) .into_any_element(); ((element.request_layout(window, cx), element), state) diff --git a/docs/docs/components/clipboard.md b/docs/docs/components/clipboard.md index 54860a9e..4e406db2 100644 --- a/docs/docs/components/clipboard.md +++ b/docs/docs/components/clipboard.md @@ -25,17 +25,12 @@ Clipboard::new("my-clipboard") }) ``` -### With Dynamic Content +### Using Dynamic Values -```rust -Clipboard::new("clipboard") - .content(|_, _| Label::new("Copy this text")) - .value("Hello, World!") -``` +The `value_fn` method allows you to provide a closure that generates the content to be copied at the time of the copy action. -### Using Value Function - -For dynamic values that should be computed when the copy action occurs: +- This is useful when the content to be copied depends on the current state of the application. +- And in some cases, it may have a larger overhead to compute, so you only want to do it when the user actually clicks the copy button. ```rust let state = some_state.clone(); @@ -53,14 +48,14 @@ Clipboard::new("dynamic-clipboard") ```rust use gpui_component::label::Label; -Clipboard::new("custom-clipboard") - .content(|_, _| - h_flex() - .gap_2() - .child(Label::new("Share URL")) - .child(Icon::new(IconName::Share)) - ) - .value("https://example.com") + h_flex() + .gap_2() + .child(Label::new("Share URL")) + .child(Icon::new(IconName::Share)) + .child( + Clipboard::new("custom-clipboard") + .value("https://example.com") + ) ``` ### In Input Fields @@ -101,12 +96,16 @@ Clipboard::new("simple") ### With User Feedback ```rust -Clipboard::new("feedback") - .content(|_, _| Label::new("API Key")) - .value("sk-1234567890abcdef") - .on_copied(|_, window, cx| { - window.push_notification("API key copied to clipboard", cx) - }) +h_flex() + .gap_2() + .child(Label::new("Your API Key:")) + .child( + Clipboard::new("feedback") + .value("sk-1234567890abcdef") + .on_copied(|_, window, cx| { + window.push_notification("API key copied to clipboard", cx) + }) + ) ``` ### Form Field Integration @@ -149,7 +148,6 @@ let app_state = cx.new(|_| AppState { }); Clipboard::new("current-url") - .content(|_, _| Label::new("Share current page")) .value_fn({ let state = app_state.clone(); move |_, cx| {