tool_output_preview.rs

  1use gpui::{AnyElement, EntityId, prelude::*};
  2use ui::{Tooltip, prelude::*};
  3
  4#[derive(IntoElement)]
  5pub struct ToolOutputPreview<F>
  6where
  7    F: Fn(bool, &mut Window, &mut App) + 'static,
  8{
  9    content: AnyElement,
 10    entity_id: EntityId,
 11    full_height: bool,
 12    total_lines: usize,
 13    collapsed_fade: bool,
 14    on_toggle: Option<F>,
 15}
 16
 17pub const COLLAPSED_LINES: usize = 10;
 18
 19impl<F> ToolOutputPreview<F>
 20where
 21    F: Fn(bool, &mut Window, &mut App) + 'static,
 22{
 23    pub fn new(content: AnyElement, entity_id: EntityId) -> Self {
 24        Self {
 25            content,
 26            entity_id,
 27            full_height: true,
 28            total_lines: 0,
 29            collapsed_fade: false,
 30            on_toggle: None,
 31        }
 32    }
 33
 34    pub fn with_total_lines(mut self, total_lines: usize) -> Self {
 35        self.total_lines = total_lines;
 36        self
 37    }
 38
 39    pub fn toggle_state(mut self, full_height: bool) -> Self {
 40        self.full_height = full_height;
 41        self
 42    }
 43
 44    pub fn with_collapsed_fade(mut self) -> Self {
 45        self.collapsed_fade = true;
 46        self
 47    }
 48
 49    pub fn on_toggle(mut self, listener: F) -> Self {
 50        self.on_toggle = Some(listener);
 51        self
 52    }
 53}
 54
 55impl<F> RenderOnce for ToolOutputPreview<F>
 56where
 57    F: Fn(bool, &mut Window, &mut App) + 'static,
 58{
 59    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
 60        if self.total_lines <= COLLAPSED_LINES {
 61            return self.content;
 62        }
 63        let border_color = cx.theme().colors().border.opacity(0.6);
 64
 65        let (icon, tooltip_label) = if self.full_height {
 66            (IconName::ChevronUp, "Collapse")
 67        } else {
 68            (IconName::ChevronDown, "Expand")
 69        };
 70
 71        let gradient_overlay =
 72            if self.collapsed_fade && !self.full_height {
 73                Some(div().absolute().bottom_5().left_0().w_full().h_2_5().bg(
 74                    gpui::linear_gradient(
 75                        0.,
 76                        gpui::linear_color_stop(cx.theme().colors().editor_background, 0.),
 77                        gpui::linear_color_stop(
 78                            cx.theme().colors().editor_background.opacity(0.),
 79                            1.,
 80                        ),
 81                    ),
 82                ))
 83            } else {
 84                None
 85            };
 86
 87        v_flex()
 88            .relative()
 89            .child(self.content)
 90            .children(gradient_overlay)
 91            .child(
 92                h_flex()
 93                    .id(("expand-button", self.entity_id))
 94                    .flex_none()
 95                    .cursor_pointer()
 96                    .h_5()
 97                    .justify_center()
 98                    .border_t_1()
 99                    .rounded_b_md()
100                    .border_color(border_color)
101                    .bg(cx.theme().colors().editor_background)
102                    .hover(|style| style.bg(cx.theme().colors().element_hover.opacity(0.1)))
103                    .child(Icon::new(icon).size(IconSize::Small).color(Color::Muted))
104                    .tooltip(Tooltip::text(tooltip_label))
105                    .when_some(self.on_toggle, |this, on_toggle| {
106                        this.on_click({
107                            move |_, window, cx| {
108                                on_toggle(!self.full_height, window, cx);
109                            }
110                        })
111                    }),
112            )
113            .into_any()
114    }
115}