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