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}