From d8ace3c683a03135fac997c712f3c384d6ff5c0e Mon Sep 17 00:00:00 2001 From: Floyd Wang Date: Fri, 13 Sep 2024 13:53:39 +0800 Subject: [PATCH] clipboard: Refactor with `Element` (#242) --- .vscode/settings.json | 3 +- crates/ui/src/clipboard.rs | 129 +++++++++++++++++++++++++++++-------- 2 files changed, 105 insertions(+), 27 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 1c565ebe..11301faf 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "rust-analyzer.check.command": "clippy" + "rust-analyzer.check.command": "clippy", + "cSpell.words": ["xsmall"] } diff --git a/crates/ui/src/clipboard.rs b/crates/ui/src/clipboard.rs index 0ce9716d..beeea9dd 100644 --- a/crates/ui/src/clipboard.rs +++ b/crates/ui/src/clipboard.rs @@ -1,6 +1,8 @@ +use std::{cell::RefCell, rc::Rc, time::Duration}; + use gpui::{ - prelude::FluentBuilder, AnyElement, ClipboardItem, ElementId, IntoElement, ParentElement, - RenderOnce, SharedString, Styled, WindowContext, + prelude::FluentBuilder, AnyElement, ClipboardItem, Element, ElementId, GlobalElementId, + IntoElement, LayoutId, ParentElement, SharedString, Styled, WindowContext, }; use crate::{ @@ -8,12 +10,11 @@ use crate::{ h_flex, IconName, Sizable as _, }; -#[derive(IntoElement)] pub struct Clipboard { id: ElementId, value: SharedString, content_builder: Option AnyElement>>, - copied_callback: Option>, + copied_callback: Option>, } impl Clipboard { @@ -44,32 +45,108 @@ impl Clipboard { where F: Fn(SharedString, &mut WindowContext) + 'static, { - self.copied_callback = Some(Box::new(handler)); + self.copied_callback = Some(Rc::new(handler)); self } } -impl RenderOnce for Clipboard { - fn render(self, cx: &mut WindowContext) -> impl IntoElement { - h_flex() - .gap_1() - .items_center() - .when_some(self.content_builder, |this, builder| { - this.child(builder(cx)) - }) - .child( - Button::new(self.id, cx) - .icon(IconName::Copy) - .ghost() - .xsmall() - .on_click(move |_, cx| { - cx.stop_propagation(); - cx.write_to_clipboard(ClipboardItem::new_string(self.value.to_string())); +impl IntoElement for Clipboard { + type Element = Self; - if let Some(copied) = &self.copied_callback { - copied(self.value.clone(), cx); - } - }), - ) + 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>, + cx: &mut WindowContext, + ) -> (LayoutId, Self::RequestLayoutState) { + cx.with_element_state::(global_id.unwrap(), |state, cx| { + let state = state.unwrap_or_default(); + + let content_element = self + .content_builder + .as_ref() + .map(|builder| builder(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, cx) + .icon(if copide_value { + IconName::Check + } else { + IconName::Copy + }) + .ghost() + .xsmall() + .when(!copide_value, |this| { + this.on_click(move |_, cx| { + cx.stop_propagation(); + cx.write_to_clipboard(ClipboardItem::new_string(value.to_string())); + *copied.borrow_mut() = true; + + let copied = copied.clone(); + cx.spawn(|cx| async move { + cx.background_executor().timer(Duration::from_secs(2)).await; + + *copied.borrow_mut() = false; + }) + .detach(); + + if let Some(callback) = &copied_callback { + callback(value.clone(), cx); + } + }) + }), + ) + .into_any_element(); + + ((element.request_layout(cx), element), state) + }) + } + + fn prepaint( + &mut self, + _: Option<&gpui::GlobalElementId>, + _: gpui::Bounds, + element: &mut Self::RequestLayoutState, + cx: &mut WindowContext, + ) { + element.prepaint(cx); + } + + fn paint( + &mut self, + _: Option<&gpui::GlobalElementId>, + _: gpui::Bounds, + element: &mut Self::RequestLayoutState, + _: &mut Self::PrepaintState, + cx: &mut WindowContext, + ) { + element.paint(cx) } }