1use gpui::{
2 elements::*, platform::MouseButton, AppContext, Entity, Subscription, View, ViewContext,
3 ViewHandle,
4};
5use itertools::Itertools;
6use search::ProjectSearchView;
7use settings::Settings;
8use workspace::{
9 item::{ItemEvent, ItemHandle},
10 ToolbarItemLocation, ToolbarItemView,
11};
12
13pub enum Event {
14 UpdateLocation,
15}
16
17pub struct Breadcrumbs {
18 pane_focused: bool,
19 active_item: Option<Box<dyn ItemHandle>>,
20 project_search: Option<ViewHandle<ProjectSearchView>>,
21 subscription: Option<Subscription>,
22}
23
24impl Breadcrumbs {
25 pub fn new() -> Self {
26 Self {
27 pane_focused: false,
28 active_item: Default::default(),
29 subscription: Default::default(),
30 project_search: Default::default(),
31 }
32 }
33}
34
35impl Entity for Breadcrumbs {
36 type Event = Event;
37}
38
39impl View for Breadcrumbs {
40 fn ui_name() -> &'static str {
41 "Breadcrumbs"
42 }
43
44 fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
45 let active_item = match &self.active_item {
46 Some(active_item) => active_item,
47 None => return Empty::new().boxed(),
48 };
49 let not_editor = active_item.downcast::<editor::Editor>().is_none();
50
51 let theme = cx.global::<Settings>().theme.clone();
52 let style = &theme.workspace.breadcrumbs;
53
54 let breadcrumbs = match active_item.breadcrumbs(&theme, cx) {
55 Some(breadcrumbs) => breadcrumbs,
56 None => return Empty::new().boxed(),
57 }
58 .into_iter()
59 .map(|breadcrumb| {
60 let text = Text::new(
61 breadcrumb.text,
62 theme.workspace.breadcrumbs.default.text.clone(),
63 );
64 if let Some(highlights) = breadcrumb.highlights {
65 text.with_highlights(highlights).boxed()
66 } else {
67 text.boxed()
68 }
69 });
70
71 let crumbs = Flex::row()
72 .with_children(Itertools::intersperse_with(breadcrumbs, || {
73 Label::new(" 〉 ", style.default.text.clone()).boxed()
74 }))
75 .constrained()
76 .with_height(theme.workspace.breadcrumb_height)
77 .contained();
78
79 if not_editor || !self.pane_focused {
80 return crumbs
81 .with_style(style.default.container)
82 .aligned()
83 .left()
84 .boxed();
85 }
86
87 MouseEventHandler::<Breadcrumbs, Breadcrumbs>::new(0, cx, |state, _| {
88 let style = style.style_for(state, false);
89 crumbs.with_style(style.container).boxed()
90 })
91 .on_click(MouseButton::Left, |_, _, cx| {
92 cx.dispatch_action(outline::Toggle);
93 })
94 .with_tooltip::<Breadcrumbs>(
95 0,
96 "Show symbol outline".to_owned(),
97 Some(Box::new(outline::Toggle)),
98 theme.tooltip.clone(),
99 cx,
100 )
101 .aligned()
102 .left()
103 .boxed()
104 }
105}
106
107impl ToolbarItemView for Breadcrumbs {
108 fn set_active_pane_item(
109 &mut self,
110 active_pane_item: Option<&dyn ItemHandle>,
111 cx: &mut ViewContext<Self>,
112 ) -> ToolbarItemLocation {
113 cx.notify();
114 self.active_item = None;
115 self.project_search = None;
116 if let Some(item) = active_pane_item {
117 let this = cx.weak_handle();
118 self.subscription = Some(item.subscribe_to_item_events(
119 cx,
120 Box::new(move |event, cx| {
121 if let Some(this) = this.upgrade(cx) {
122 if let ItemEvent::UpdateBreadcrumbs = event {
123 this.update(cx, |_, cx| {
124 cx.emit(Event::UpdateLocation);
125 cx.notify();
126 });
127 }
128 }
129 }),
130 ));
131 self.active_item = Some(item.boxed_clone());
132 item.breadcrumb_location(cx)
133 } else {
134 ToolbarItemLocation::Hidden
135 }
136 }
137
138 fn location_for_event(
139 &self,
140 _: &Event,
141 current_location: ToolbarItemLocation,
142 cx: &AppContext,
143 ) -> ToolbarItemLocation {
144 if let Some(active_item) = self.active_item.as_ref() {
145 active_item.breadcrumb_location(cx)
146 } else {
147 current_location
148 }
149 }
150
151 fn pane_focus_update(&mut self, pane_focused: bool, _: &mut gpui::AppContext) {
152 self.pane_focused = pane_focused;
153 }
154}