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(Label::new(context.name.clone()).size(LabelSize::Small))
130 .when_some(context.parent.as_ref(), |element, parent_name| {
131 if *dupe_name {
132 element.child(
133 Label::new(parent_name.clone())
134 .size(LabelSize::XSmall)
135 .color(Color::Muted),
136 )
137 } else {
138 element
139 }
140 })
141 .when_some(context.tooltip.clone(), |element, tooltip| {
142 element.tooltip(Tooltip::text(tooltip.clone()))
143 }),
144 )
145 .when_some(on_remove.as_ref(), |element, on_remove| {
146 element.child(
147 IconButton::new(("remove", context.id.0), IconName::Close)
148 .shape(IconButtonShape::Square)
149 .icon_size(IconSize::XSmall)
150 .tooltip(Tooltip::text("Remove Context"))
151 .on_click({
152 let on_remove = on_remove.clone();
153 move |event, window, cx| on_remove(event, window, cx)
154 }),
155 )
156 })
157 .when_some(on_click.as_ref(), |element, on_click| {
158 let on_click = on_click.clone();
159 element.on_click(move |event, window, cx| on_click(event, window, cx))
160 }),
161 ContextPill::Suggested {
162 name,
163 icon_path: _,
164 kind,
165 focused,
166 on_click,
167 } => base_pill
168 .cursor_pointer()
169 .pr_1()
170 .border_color(if *focused {
171 color.border_focused
172 } else {
173 color.border_variant.opacity(0.5)
174 })
175 .hover(|style| style.bg(color.element_hover.opacity(0.5)))
176 .child(
177 Label::new(name.clone())
178 .size(LabelSize::Small)
179 .color(Color::Muted),
180 )
181 .child(
182 div().px_0p5().child(
183 Label::new(match kind {
184 ContextKind::File => "Active Tab",
185 ContextKind::Thread
186 | ContextKind::Directory
187 | ContextKind::FetchedUrl => "Active",
188 })
189 .size(LabelSize::XSmall)
190 .color(Color::Muted),
191 ),
192 )
193 .child(
194 Icon::new(IconName::Plus)
195 .size(IconSize::XSmall)
196 .into_any_element(),
197 )
198 .tooltip(|window, cx| {
199 Tooltip::with_meta("Suggested Context", None, "Click to add it", window, cx)
200 })
201 .when_some(on_click.as_ref(), |element, on_click| {
202 let on_click = on_click.clone();
203 element.on_click(move |event, window, cx| on_click(event, window, cx))
204 }),
205 }
206 }
207}