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