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 let pane = pane.clone();
142 cx.window_context().defer(move |cx| {
143 workspace.update(cx, |workspace, cx| {
144 Pane::go_back(workspace, Some(pane.clone()), cx)
145 .detach_and_log_err(cx);
146 });
147 })
148 }
149 }
150 },
151 super::GoBack { pane: None },
152 "Go Back",
153 cx,
154 ))
155 .with_child(nav_button(
156 "icons/arrow_right_16.svg",
157 button_style,
158 tooltip_style,
159 enable_go_forward,
160 spacing,
161 {
162 let pane = pane.clone();
163 move |toolbar, cx| {
164 if let Some(workspace) = toolbar
165 .pane
166 .upgrade(cx)
167 .and_then(|pane| pane.read(cx).workspace().upgrade(cx))
168 {
169 let pane = pane.clone();
170 cx.window_context().defer(move |cx| {
171 workspace.update(cx, |workspace, cx| {
172 Pane::go_forward(workspace, Some(pane.clone()), cx)
173 .detach_and_log_err(cx);
174 });
175 });
176 }
177 }
178 },
179 super::GoForward { pane: None },
180 "Go Forward",
181 cx,
182 ))
183 .with_children(primary_left_items)
184 .with_children(primary_right_items)
185 .constrained()
186 .with_height(height),
187 )
188 .with_children(secondary_item)
189 .contained()
190 .with_style(container_style)
191 .into_any_named("toolbar")
192 }
193}
194
195#[allow(clippy::too_many_arguments)]
196fn nav_button<A: Action, F: 'static + Fn(&mut Toolbar, &mut ViewContext<Toolbar>)>(
197 svg_path: &'static str,
198 style: theme::Interactive<theme::IconButton>,
199 tooltip_style: TooltipStyle,
200 enabled: bool,
201 spacing: f32,
202 on_click: F,
203 tooltip_action: A,
204 action_name: &str,
205 cx: &mut ViewContext<Toolbar>,
206) -> AnyElement<Toolbar> {
207 MouseEventHandler::<A, _>::new(0, cx, |state, _| {
208 let style = if enabled {
209 style.style_for(state, false)
210 } else {
211 style.disabled_style()
212 };
213 Svg::new(svg_path)
214 .with_color(style.color)
215 .constrained()
216 .with_width(style.icon_width)
217 .aligned()
218 .contained()
219 .with_style(style.container)
220 .constrained()
221 .with_width(style.button_width)
222 .with_height(style.button_width)
223 .aligned()
224 })
225 .with_cursor_style(if enabled {
226 CursorStyle::PointingHand
227 } else {
228 CursorStyle::default()
229 })
230 .on_click(MouseButton::Left, move |_, toolbar, cx| {
231 on_click(toolbar, cx)
232 })
233 .with_tooltip::<A>(
234 0,
235 action_name.to_string(),
236 Some(Box::new(tooltip_action)),
237 tooltip_style,
238 cx,
239 )
240 .contained()
241 .with_margin_right(spacing)
242 .into_any_named("nav button")
243}
244
245impl Toolbar {
246 pub fn new(pane: WeakViewHandle<Pane>) -> Self {
247 Self {
248 active_pane_item: None,
249 pane,
250 items: Default::default(),
251 hidden: false,
252 }
253 }
254
255 pub fn add_item<T>(&mut self, item: ViewHandle<T>, cx: &mut ViewContext<Self>)
256 where
257 T: 'static + ToolbarItemView,
258 {
259 let location = item.set_active_pane_item(self.active_pane_item.as_deref(), cx);
260 cx.subscribe(&item, |this, item, event, cx| {
261 if let Some((_, current_location)) =
262 this.items.iter_mut().find(|(i, _)| i.id() == item.id())
263 {
264 let new_location = item
265 .read(cx)
266 .location_for_event(event, *current_location, cx);
267 if new_location != *current_location {
268 *current_location = new_location;
269 cx.notify();
270 }
271 }
272 })
273 .detach();
274 self.items.push((Box::new(item), location));
275 cx.notify();
276 }
277
278 pub fn set_active_pane_item(
279 &mut self,
280 pane_item: Option<&dyn ItemHandle>,
281 cx: &mut ViewContext<Self>,
282 ) {
283 self.active_pane_item = pane_item.map(|item| item.boxed_clone());
284 self.hidden = self
285 .active_pane_item
286 .as_ref()
287 .map(|item| !item.show_toolbar(cx))
288 .unwrap_or(false);
289
290 for (toolbar_item, current_location) in self.items.iter_mut() {
291 let new_location = toolbar_item.set_active_pane_item(pane_item, cx);
292 if new_location != *current_location {
293 *current_location = new_location;
294 cx.notify();
295 }
296 }
297 }
298
299 pub fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut ViewContext<Self>) {
300 for (toolbar_item, _) in self.items.iter_mut() {
301 toolbar_item.pane_focus_update(pane_focused, cx);
302 }
303 }
304
305 pub fn item_of_type<T: ToolbarItemView>(&self) -> Option<ViewHandle<T>> {
306 self.items
307 .iter()
308 .find_map(|(item, _)| item.as_any().clone().downcast())
309 }
310
311 pub fn hidden(&self) -> bool {
312 self.hidden
313 }
314}
315
316impl<T: ToolbarItemView> ToolbarItemViewHandle for ViewHandle<T> {
317 fn id(&self) -> usize {
318 self.id()
319 }
320
321 fn as_any(&self) -> &AnyViewHandle {
322 self
323 }
324
325 fn set_active_pane_item(
326 &self,
327 active_pane_item: Option<&dyn ItemHandle>,
328 cx: &mut WindowContext,
329 ) -> ToolbarItemLocation {
330 self.update(cx, |this, cx| {
331 this.set_active_pane_item(active_pane_item, cx)
332 })
333 }
334
335 fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut WindowContext) {
336 self.update(cx, |this, cx| {
337 this.pane_focus_update(pane_focused, cx);
338 cx.notify();
339 });
340 }
341}
342
343impl From<&dyn ToolbarItemViewHandle> for AnyViewHandle {
344 fn from(val: &dyn ToolbarItemViewHandle) -> Self {
345 val.as_any().clone()
346 }
347}