1use std::time::Duration;
2
3use gpui::{Animation, AnimationExt, AnyView, IntoElement, Window, pulsating_between};
4use settings::Settings;
5use theme::ThemeSettings;
6use ui::{ButtonLike, TintColor, Tooltip, prelude::*};
7
8#[derive(IntoElement)]
9pub struct MentionCrease {
10 id: ElementId,
11 icon: SharedString,
12 label: SharedString,
13 is_toggled: bool,
14 is_loading: bool,
15 tooltip: Option<SharedString>,
16 image_preview: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyView + 'static>>,
17}
18
19impl MentionCrease {
20 pub fn new(
21 id: impl Into<ElementId>,
22 icon: impl Into<SharedString>,
23 label: impl Into<SharedString>,
24 ) -> Self {
25 Self {
26 id: id.into(),
27 icon: icon.into(),
28 label: label.into(),
29 is_toggled: false,
30 is_loading: false,
31 tooltip: None,
32 image_preview: None,
33 }
34 }
35
36 pub fn is_toggled(mut self, is_toggled: bool) -> Self {
37 self.is_toggled = is_toggled;
38 self
39 }
40
41 pub fn is_loading(mut self, is_loading: bool) -> Self {
42 self.is_loading = is_loading;
43 self
44 }
45
46 pub fn tooltip(mut self, tooltip: impl Into<SharedString>) -> Self {
47 self.tooltip = Some(tooltip.into());
48 self
49 }
50
51 pub fn image_preview(
52 mut self,
53 builder: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
54 ) -> Self {
55 self.image_preview = Some(Box::new(builder));
56 self
57 }
58}
59
60impl RenderOnce for MentionCrease {
61 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
62 let settings = ThemeSettings::get_global(cx);
63 let font_size = settings.agent_buffer_font_size(cx);
64 let buffer_font = settings.buffer_font.clone();
65 let is_loading = self.is_loading;
66 let tooltip = self.tooltip;
67 let image_preview = self.image_preview;
68
69 let button_height = DefiniteLength::Absolute(AbsoluteLength::Pixels(
70 px(window.line_height().into()) - px(1.),
71 ));
72
73 ButtonLike::new(self.id)
74 .style(ButtonStyle::Outlined)
75 .size(ButtonSize::Compact)
76 .height(button_height)
77 .selected_style(ButtonStyle::Tinted(TintColor::Accent))
78 .toggle_state(self.is_toggled)
79 .child(
80 h_flex()
81 .pb_px()
82 .gap_1()
83 .font(buffer_font)
84 .text_size(font_size)
85 .child(
86 Icon::from_path(self.icon.clone())
87 .size(IconSize::XSmall)
88 .color(Color::Muted),
89 )
90 .child(self.label.clone())
91 .map(|this| {
92 if is_loading {
93 this.with_animation(
94 "loading-context-crease",
95 Animation::new(Duration::from_secs(2))
96 .repeat()
97 .with_easing(pulsating_between(0.4, 0.8)),
98 |label, delta| label.opacity(delta),
99 )
100 .into_any()
101 } else {
102 this.into_any()
103 }
104 }),
105 )
106 .map(|button| {
107 if let Some(image_preview) = image_preview {
108 button.hoverable_tooltip(image_preview)
109 } else {
110 button.when_some(tooltip, |this, tooltip_text| {
111 this.tooltip(Tooltip::text(tooltip_text))
112 })
113 }
114 })
115 }
116}