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 on_remove: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
14 },
15 Suggested {
16 name: SharedString,
17 icon_path: Option<SharedString>,
18 kind: ContextKind,
19 on_add: Rc<dyn Fn(&ClickEvent, &mut WindowContext)>,
20 },
21}
22
23impl ContextPill {
24 pub fn new_added(
25 context: ContextSnapshot,
26 dupe_name: bool,
27 on_remove: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
28 ) -> Self {
29 Self::Added {
30 context,
31 dupe_name,
32 on_remove,
33 }
34 }
35
36 pub fn new_suggested(
37 name: SharedString,
38 icon_path: Option<SharedString>,
39 kind: ContextKind,
40 on_add: Rc<dyn Fn(&ClickEvent, &mut WindowContext)>,
41 ) -> Self {
42 Self::Suggested {
43 name,
44 icon_path,
45 kind,
46 on_add,
47 }
48 }
49
50 pub fn id(&self) -> ElementId {
51 match self {
52 Self::Added { context, .. } => {
53 ElementId::NamedInteger("context-pill".into(), context.id.0)
54 }
55 Self::Suggested { .. } => "suggested-context-pill".into(),
56 }
57 }
58
59 pub fn icon(&self) -> Icon {
60 match self {
61 Self::Added { context, .. } => match &context.icon_path {
62 Some(icon_path) => Icon::from_path(icon_path),
63 None => Icon::new(context.kind.icon()),
64 },
65 Self::Suggested {
66 icon_path: Some(icon_path),
67 ..
68 } => Icon::from_path(icon_path),
69 Self::Suggested {
70 kind,
71 icon_path: None,
72 ..
73 } => Icon::new(kind.icon()),
74 }
75 }
76}
77
78impl RenderOnce for ContextPill {
79 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
80 let color = cx.theme().colors();
81
82 let base_pill = h_flex()
83 .id(self.id())
84 .pl_1()
85 .pb(px(1.))
86 .border_1()
87 .rounded_md()
88 .gap_1()
89 .child(self.icon().size(IconSize::XSmall).color(Color::Muted));
90
91 match &self {
92 ContextPill::Added {
93 context,
94 dupe_name,
95 on_remove,
96 } => base_pill
97 .bg(color.element_background)
98 .border_color(color.border.opacity(0.5))
99 .pr(if on_remove.is_some() { px(2.) } else { px(4.) })
100 .child(
101 h_flex()
102 .id("context-data")
103 .gap_1()
104 .child(Label::new(context.name.clone()).size(LabelSize::Small))
105 .when_some(context.parent.as_ref(), |element, parent_name| {
106 if *dupe_name {
107 element.child(
108 Label::new(parent_name.clone())
109 .size(LabelSize::XSmall)
110 .color(Color::Muted),
111 )
112 } else {
113 element
114 }
115 })
116 .when_some(context.tooltip.clone(), |element, tooltip| {
117 element.tooltip(move |cx| Tooltip::text(tooltip.clone(), cx))
118 }),
119 )
120 .when_some(on_remove.as_ref(), |element, on_remove| {
121 element.child(
122 IconButton::new(("remove", context.id.0), IconName::Close)
123 .shape(IconButtonShape::Square)
124 .icon_size(IconSize::XSmall)
125 .tooltip(|cx| Tooltip::text("Remove Context", cx))
126 .on_click({
127 let on_remove = on_remove.clone();
128 move |event, cx| on_remove(event, cx)
129 }),
130 )
131 }),
132 ContextPill::Suggested {
133 name,
134 icon_path: _,
135 kind,
136 on_add,
137 } => base_pill
138 .cursor_pointer()
139 .pr_1()
140 .border_color(color.border_variant.opacity(0.5))
141 .hover(|style| style.bg(color.element_hover.opacity(0.5)))
142 .child(
143 Label::new(name.clone())
144 .size(LabelSize::Small)
145 .color(Color::Muted),
146 )
147 .child(
148 div().px_0p5().child(
149 Label::new(match kind {
150 ContextKind::File => "Active Tab",
151 ContextKind::Thread
152 | ContextKind::Directory
153 | ContextKind::FetchedUrl => "Active",
154 })
155 .size(LabelSize::XSmall)
156 .color(Color::Muted),
157 ),
158 )
159 .child(
160 Icon::new(IconName::Plus)
161 .size(IconSize::XSmall)
162 .into_any_element(),
163 )
164 .tooltip(|cx| Tooltip::with_meta("Suggested Context", None, "Click to add it", cx))
165 .on_click({
166 let on_add = on_add.clone();
167 move |event, cx| on_add(event, cx)
168 }),
169 }
170 }
171}