1use crate::{ItemHandle, Pane};
2use gpui::{
3 elements::*, platform::CursorStyle, platform::MouseButton, Action, AnyElement, AnyViewHandle,
4 AppContext, Entity, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
5};
6use settings::Settings;
7
8pub trait ToolbarItemView: View {
9 fn set_active_pane_item(
10 &mut self,
11 active_pane_item: Option<&dyn crate::ItemHandle>,
12 cx: &mut ViewContext<Self>,
13 ) -> ToolbarItemLocation;
14
15 fn location_for_event(
16 &self,
17 _event: &Self::Event,
18 current_location: ToolbarItemLocation,
19 _cx: &AppContext,
20 ) -> ToolbarItemLocation {
21 current_location
22 }
23
24 fn pane_focus_update(&mut self, _pane_focused: bool, _cx: &mut ViewContext<Self>) {}
25}
26
27trait ToolbarItemViewHandle {
28 fn id(&self) -> usize;
29 fn as_any(&self) -> &AnyViewHandle;
30 fn set_active_pane_item(
31 &self,
32 active_pane_item: Option<&dyn ItemHandle>,
33 cx: &mut WindowContext,
34 ) -> ToolbarItemLocation;
35 fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut WindowContext);
36}
37
38#[derive(Copy, Clone, Debug, PartialEq)]
39pub enum ToolbarItemLocation {
40 Hidden,
41 PrimaryLeft { flex: Option<(f32, bool)> },
42 PrimaryRight { flex: Option<(f32, bool)> },
43 Secondary,
44}
45
46pub struct Toolbar {
47 active_pane_item: Option<Box<dyn ItemHandle>>,
48 hidden: bool,
49 pane: WeakViewHandle<Pane>,
50 items: Vec<(Box<dyn ToolbarItemViewHandle>, ToolbarItemLocation)>,
51}
52
53impl Entity for Toolbar {
54 type Event = ();
55}
56
57impl View for Toolbar {
58 fn ui_name() -> &'static str {
59 "Toolbar"
60 }
61
62 fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
63 let theme = &cx.global::<Settings>().theme.workspace.toolbar;
64
65 let mut primary_left_items = Vec::new();
66 let mut primary_right_items = Vec::new();
67 let mut secondary_item = None;
68 let spacing = theme.item_spacing;
69
70 for (item, position) in &self.items {
71 match *position {
72 ToolbarItemLocation::Hidden => {}
73
74 ToolbarItemLocation::PrimaryLeft { flex } => {
75 let left_item = ChildView::new(item.as_any(), cx)
76 .aligned()
77 .contained()
78 .with_margin_right(spacing);
79 if let Some((flex, expanded)) = flex {
80 primary_left_items.push(left_item.flex(flex, expanded).into_any());
81 } else {
82 primary_left_items.push(left_item.into_any());
83 }
84 }
85
86 ToolbarItemLocation::PrimaryRight { flex } => {
87 let right_item = ChildView::new(item.as_any(), cx)
88 .aligned()
89 .contained()
90 .with_margin_left(spacing)
91 .flex_float();
92 if let Some((flex, expanded)) = flex {
93 primary_right_items.push(right_item.flex(flex, expanded).into_any());
94 } else {
95 primary_right_items.push(right_item.into_any());
96 }
97 }
98
99 ToolbarItemLocation::Secondary => {
100 secondary_item = Some(
101 ChildView::new(item.as_any(), cx)
102 .constrained()
103 .with_height(theme.height)
104 .into_any(),
105 );
106 }
107 }
108 }
109
110 let pane = self.pane.clone();
111 let mut enable_go_backward = false;
112 let mut enable_go_forward = false;
113 if let Some(pane) = pane.upgrade(cx) {
114 let pane = pane.read(cx);
115 enable_go_backward = pane.can_navigate_backward();
116 enable_go_forward = pane.can_navigate_forward();
117 }
118
119 let container_style = theme.container;
120 let height = theme.height;
121 let button_style = theme.nav_button;
122 let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
123
124 Flex::column()
125 .with_child(
126 Flex::row()
127 .with_child(nav_button(
128 "icons/arrow_left_16.svg",
129 button_style,
130 tooltip_style.clone(),
131 enable_go_backward,
132 spacing,
133 {
134 let pane = pane.clone();
135 move |toolbar, cx| {
136 if let Some(workspace) = toolbar
137 .pane
138 .upgrade(cx)
139 .and_then(|pane| pane.read(cx).workspace().upgrade(cx))
140 {
141 workspace.update(cx, |workspace, cx| {
142 Pane::go_back(workspace, Some(pane.clone()), cx)
143 .detach_and_log_err(cx);
144 });
145 }
146 }
147 },
148 super::GoBack { pane: None },
149 "Go Back",
150 cx,
151 ))
152 .with_child(nav_button(
153 "icons/arrow_right_16.svg",
154 button_style,
155 tooltip_style,
156 enable_go_forward,
157 spacing,
158 {
159 let pane = pane.clone();
160 move |toolbar, cx| {
161 if let Some(workspace) = toolbar
162 .pane
163 .upgrade(cx)
164 .and_then(|pane| pane.read(cx).workspace().upgrade(cx))
165 {
166 workspace.update(cx, |workspace, cx| {
167 Pane::go_forward(workspace, Some(pane.clone()), cx)
168 .detach_and_log_err(cx);
169 });
170 }
171 }
172 },
173 super::GoForward { pane: None },
174 "Go Forward",
175 cx,
176 ))
177 .with_children(primary_left_items)
178 .with_children(primary_right_items)
179 .constrained()
180 .with_height(height),
181 )
182 .with_children(secondary_item)
183 .contained()
184 .with_style(container_style)
185 .into_any_named("toolbar")
186 }
187}
188
189#[allow(clippy::too_many_arguments)]
190fn nav_button<A: Action, F: 'static + Fn(&mut Toolbar, &mut ViewContext<Toolbar>)>(
191 svg_path: &'static str,
192 style: theme::Interactive<theme::IconButton>,
193 tooltip_style: TooltipStyle,
194 enabled: bool,
195 spacing: f32,
196 on_click: F,
197 tooltip_action: A,
198 action_name: &str,
199 cx: &mut ViewContext<Toolbar>,
200) -> AnyElement<Toolbar> {
201 MouseEventHandler::<A, _>::new(0, cx, |state, _| {
202 let style = if enabled {
203 style.style_for(state, false)
204 } else {
205 style.disabled_style()
206 };
207 Svg::new(svg_path)
208 .with_color(style.color)
209 .constrained()
210 .with_width(style.icon_width)
211 .aligned()
212 .contained()
213 .with_style(style.container)
214 .constrained()
215 .with_width(style.button_width)
216 .with_height(style.button_width)
217 .aligned()
218 })
219 .with_cursor_style(if enabled {
220 CursorStyle::PointingHand
221 } else {
222 CursorStyle::default()
223 })
224 .on_click(MouseButton::Left, move |_, toolbar, cx| {
225 on_click(toolbar, cx)
226 })
227 .with_tooltip::<A>(
228 0,
229 action_name.to_string(),
230 Some(Box::new(tooltip_action)),
231 tooltip_style,
232 cx,
233 )
234 .contained()
235 .with_margin_right(spacing)
236 .into_any_named("nav button")
237}
238
239impl Toolbar {
240 pub fn new(pane: WeakViewHandle<Pane>) -> Self {
241 Self {
242 active_pane_item: None,
243 pane,
244 items: Default::default(),
245 hidden: false,
246 }
247 }
248
249 pub fn add_item<T>(&mut self, item: ViewHandle<T>, cx: &mut ViewContext<Self>)
250 where
251 T: 'static + ToolbarItemView,
252 {
253 let location = item.set_active_pane_item(self.active_pane_item.as_deref(), cx);
254 cx.subscribe(&item, |this, item, event, cx| {
255 if let Some((_, current_location)) =
256 this.items.iter_mut().find(|(i, _)| i.id() == item.id())
257 {
258 let new_location = item
259 .read(cx)
260 .location_for_event(event, *current_location, cx);
261 if new_location != *current_location {
262 *current_location = new_location;
263 cx.notify();
264 }
265 }
266 })
267 .detach();
268 self.items.push((Box::new(item), location));
269 cx.notify();
270 }
271
272 pub fn set_active_pane_item(
273 &mut self,
274 pane_item: Option<&dyn ItemHandle>,
275 cx: &mut ViewContext<Self>,
276 ) {
277 self.active_pane_item = pane_item.map(|item| item.boxed_clone());
278 self.hidden = self
279 .active_pane_item
280 .as_ref()
281 .map(|item| !item.show_toolbar(cx))
282 .unwrap_or(false);
283
284 for (toolbar_item, current_location) in self.items.iter_mut() {
285 let new_location = toolbar_item.set_active_pane_item(pane_item, cx);
286 if new_location != *current_location {
287 *current_location = new_location;
288 cx.notify();
289 }
290 }
291 }
292
293 pub fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut ViewContext<Self>) {
294 for (toolbar_item, _) in self.items.iter_mut() {
295 toolbar_item.pane_focus_update(pane_focused, cx);
296 }
297 }
298
299 pub fn item_of_type<T: ToolbarItemView>(&self) -> Option<ViewHandle<T>> {
300 self.items
301 .iter()
302 .find_map(|(item, _)| item.as_any().clone().downcast())
303 }
304
305 pub fn hidden(&self) -> bool {
306 self.hidden
307 }
308}
309
310impl<T: ToolbarItemView> ToolbarItemViewHandle for ViewHandle<T> {
311 fn id(&self) -> usize {
312 self.id()
313 }
314
315 fn as_any(&self) -> &AnyViewHandle {
316 self
317 }
318
319 fn set_active_pane_item(
320 &self,
321 active_pane_item: Option<&dyn ItemHandle>,
322 cx: &mut WindowContext,
323 ) -> ToolbarItemLocation {
324 self.update(cx, |this, cx| {
325 this.set_active_pane_item(active_pane_item, cx)
326 })
327 }
328
329 fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut WindowContext) {
330 self.update(cx, |this, cx| {
331 this.pane_focus_update(pane_focused, cx);
332 cx.notify();
333 });
334 }
335}
336
337impl From<&dyn ToolbarItemViewHandle> for AnyViewHandle {
338 fn from(val: &dyn ToolbarItemViewHandle) -> Self {
339 val.as_any().clone()
340 }
341}