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.
This commit is contained in:
Jason Lee 2025-11-05 15:17:58 +08:00 committed by GitHub
parent d3ec1f29f8
commit 2093046942
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 77 additions and 89 deletions

View file

@ -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(

View file

@ -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<Rc<dyn Fn(&mut Window, &mut App) -> SharedString>>,
content_builder: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyElement>>,
copied_callback: Option<Rc<dyn Fn(SharedString, &mut Window, &mut App)>>,
}
impl Clipboard {
/// Create a new Clipboard element with the given ID.
pub fn new(id: impl Into<ElementId>) -> 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<SharedString>) -> 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<F>(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<E, F>(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<bool>,
@ -101,10 +93,6 @@ impl Element for Clipboard {
window.with_element_state::<ClipboardState, _>(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)

View file

@ -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| {