use gpui::{ prelude::FluentBuilder, AnyElement, App, ClipboardItem, Element, ElementId, GlobalElementId, IntoElement, LayoutId, ParentElement, SharedString, Styled, Window, }; use std::{cell::RefCell, rc::Rc, time::Duration}; use crate::{ button::{Button, ButtonVariants as _}, h_flex, IconName, Sizable as _, }; type ContentBuilder = Option AnyElement>>; type CopiedCallback = Option>; pub struct Clipboard { id: ElementId, value: SharedString, content_builder: ContentBuilder, copied_callback: CopiedCallback, } impl Clipboard { pub fn new(id: impl Into) -> Self { Self { id: id.into(), value: "".into(), content_builder: None, copied_callback: None, } } pub fn value(mut self, value: impl Into) -> Self { self.value = value.into(); 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 } pub fn on_copied(mut self, handler: F) -> Self where F: Fn(SharedString, &mut Window, &mut App) + 'static, { self.copied_callback = Some(Rc::new(handler)); self } } impl IntoElement for Clipboard { type Element = Self; fn into_element(self) -> Self::Element { self } } #[derive(Default)] pub struct ClipboardState { copied: Rc>, } impl Element for Clipboard { type RequestLayoutState = AnyElement; type PrepaintState = (); fn id(&self) -> Option { Some(self.id.clone()) } fn request_layout( &mut self, global_id: Option<&GlobalElementId>, window: &mut Window, cx: &mut App, ) -> (LayoutId, Self::RequestLayoutState) { 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()); let copied = state.copied.clone(); let copide_value = *copied.borrow(); 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 }) .ghost() .xsmall() .when(!copide_value, |this| { this.on_click(move |_, window, cx| { cx.stop_propagation(); cx.write_to_clipboard(ClipboardItem::new_string(value.to_string())); *copied.borrow_mut() = true; let copied = copied.clone(); cx.spawn(async move |cx| { cx.background_executor().timer(Duration::from_secs(2)).await; *copied.borrow_mut() = false; }) .detach(); if let Some(callback) = &copied_callback { callback(value.clone(), window, cx); } }) }), ) .into_any_element(); ((element.request_layout(window, cx), element), state) }) } fn prepaint( &mut self, _: Option<&gpui::GlobalElementId>, _: gpui::Bounds, element: &mut Self::RequestLayoutState, window: &mut Window, cx: &mut App, ) { element.prepaint(window, cx); } fn paint( &mut self, _: Option<&gpui::GlobalElementId>, _: gpui::Bounds, element: &mut Self::RequestLayoutState, _: &mut Self::PrepaintState, window: &mut Window, cx: &mut App, ) { element.paint(window, cx) } }