context_pill.rs

  1use std::rc::Rc;
  2
  3use gpui::ClickEvent;
  4use ui::{prelude::*, IconButtonShape, Tooltip};
  5
  6use crate::context::{ContextKind, ContextSnapshot};
  7
  8#[derive(IntoElement)]
  9pub enum ContextPill {
 10    Added {
 11        context: ContextSnapshot,
 12        dupe_name: bool,
 13        focused: bool,
 14        on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
 15        on_remove: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
 16    },
 17    Suggested {
 18        name: SharedString,
 19        icon_path: Option<SharedString>,
 20        kind: ContextKind,
 21        focused: bool,
 22        on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
 23    },
 24}
 25
 26impl ContextPill {
 27    pub fn added(
 28        context: ContextSnapshot,
 29        dupe_name: bool,
 30        focused: bool,
 31        on_remove: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
 32    ) -> Self {
 33        Self::Added {
 34            context,
 35            dupe_name,
 36            on_remove,
 37            focused,
 38            on_click: None,
 39        }
 40    }
 41
 42    pub fn suggested(
 43        name: SharedString,
 44        icon_path: Option<SharedString>,
 45        kind: ContextKind,
 46        focused: bool,
 47    ) -> Self {
 48        Self::Suggested {
 49            name,
 50            icon_path,
 51            kind,
 52            focused,
 53            on_click: None,
 54        }
 55    }
 56
 57    pub fn on_click(mut self, listener: Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>) -> Self {
 58        match &mut self {
 59            ContextPill::Added { on_click, .. } => {
 60                *on_click = Some(listener);
 61            }
 62            ContextPill::Suggested { on_click, .. } => {
 63                *on_click = Some(listener);
 64            }
 65        }
 66        self
 67    }
 68
 69    pub fn id(&self) -> ElementId {
 70        match self {
 71            Self::Added { context, .. } => {
 72                ElementId::NamedInteger("context-pill".into(), context.id.0)
 73            }
 74            Self::Suggested { .. } => "suggested-context-pill".into(),
 75        }
 76    }
 77
 78    pub fn icon(&self) -> Icon {
 79        match self {
 80            Self::Added { context, .. } => match &context.icon_path {
 81                Some(icon_path) => Icon::from_path(icon_path),
 82                None => Icon::new(context.kind.icon()),
 83            },
 84            Self::Suggested {
 85                icon_path: Some(icon_path),
 86                ..
 87            } => Icon::from_path(icon_path),
 88            Self::Suggested {
 89                kind,
 90                icon_path: None,
 91                ..
 92            } => Icon::new(kind.icon()),
 93        }
 94    }
 95}
 96
 97impl RenderOnce for ContextPill {
 98    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
 99        let color = cx.theme().colors();
100
101        let base_pill = h_flex()
102            .id(self.id())
103            .pl_1()
104            .pb(px(1.))
105            .border_1()
106            .rounded_sm()
107            .gap_1()
108            .child(self.icon().size(IconSize::XSmall).color(Color::Muted));
109
110        match &self {
111            ContextPill::Added {
112                context,
113                dupe_name,
114                on_remove,
115                focused,
116                on_click,
117            } => base_pill
118                .bg(color.element_background)
119                .border_color(if *focused {
120                    color.border_focused
121                } else {
122                    color.border.opacity(0.5)
123                })
124                .pr(if on_remove.is_some() { px(2.) } else { px(4.) })
125                .child(
126                    h_flex()
127                        .id("context-data")
128                        .gap_1()
129                        .child(
130                            div().max_w_64().child(
131                                Label::new(context.name.clone())
132                                    .size(LabelSize::Small)
133                                    .truncate(),
134                            ),
135                        )
136                        .when_some(context.parent.as_ref(), |element, parent_name| {
137                            if *dupe_name {
138                                element.child(
139                                    Label::new(parent_name.clone())
140                                        .size(LabelSize::XSmall)
141                                        .color(Color::Muted),
142                                )
143                            } else {
144                                element
145                            }
146                        })
147                        .when_some(context.tooltip.clone(), |element, tooltip| {
148                            element.tooltip(Tooltip::text(tooltip.clone()))
149                        }),
150                )
151                .when_some(on_remove.as_ref(), |element, on_remove| {
152                    element.child(
153                        IconButton::new(("remove", context.id.0), IconName::Close)
154                            .shape(IconButtonShape::Square)
155                            .icon_size(IconSize::XSmall)
156                            .tooltip(Tooltip::text("Remove Context"))
157                            .on_click({
158                                let on_remove = on_remove.clone();
159                                move |event, window, cx| on_remove(event, window, cx)
160                            }),
161                    )
162                })
163                .when_some(on_click.as_ref(), |element, on_click| {
164                    let on_click = on_click.clone();
165                    element.on_click(move |event, window, cx| on_click(event, window, cx))
166                }),
167            ContextPill::Suggested {
168                name,
169                icon_path: _,
170                kind,
171                focused,
172                on_click,
173            } => base_pill
174                .cursor_pointer()
175                .pr_1()
176                .border_color(if *focused {
177                    color.border_focused
178                } else {
179                    color.border_variant.opacity(0.5)
180                })
181                .hover(|style| style.bg(color.element_hover.opacity(0.5)))
182                .child(
183                    div().px_0p5().max_w_64().child(
184                        Label::new(name.clone())
185                            .size(LabelSize::Small)
186                            .color(Color::Muted)
187                            .truncate(),
188                    ),
189                )
190                .child(
191                    Label::new(match kind {
192                        ContextKind::File => "Active Tab",
193                        ContextKind::Thread | ContextKind::Directory | ContextKind::FetchedUrl => {
194                            "Active"
195                        }
196                    })
197                    .size(LabelSize::XSmall)
198                    .color(Color::Muted),
199                )
200                .child(
201                    Icon::new(IconName::Plus)
202                        .size(IconSize::XSmall)
203                        .into_any_element(),
204                )
205                .tooltip(|window, cx| {
206                    Tooltip::with_meta("Suggested Context", None, "Click to add it", window, cx)
207                })
208                .when_some(on_click.as_ref(), |element, on_click| {
209                    let on_click = on_click.clone();
210                    element.on_click(move |event, window, cx| on_click(event, window, cx))
211                }),
212        }
213    }
214}