1use crate::{ItemHandle, Pane};
2use gpui::{
3 elements::*, platform::CursorStyle, platform::MouseButton, Action, AnyViewHandle, AppContext,
4 ElementBox, Entity, RenderContext, View, ViewContext, ViewHandle, WeakViewHandle,
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 AppContext) {}
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 AppContext,
34 ) -> ToolbarItemLocation;
35 fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut AppContext);
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 RenderContext<Self>) -> ElementBox {
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 ToolbarItemLocation::PrimaryLeft { flex } => {
74 let left_item = ChildView::new(item.as_any(), cx)
75 .aligned()
76 .contained()
77 .with_margin_right(spacing);
78 if let Some((flex, expanded)) = flex {
79 primary_left_items.push(left_item.flex(flex, expanded).boxed());
80 } else {
81 primary_left_items.push(left_item.boxed());
82 }
83 }
84 ToolbarItemLocation::PrimaryRight { flex } => {
85 let right_item = ChildView::new(item.as_any(), cx)
86 .aligned()
87 .contained()
88 .with_margin_left(spacing)
89 .flex_float();
90 if let Some((flex, expanded)) = flex {
91 primary_right_items.push(right_item.flex(flex, expanded).boxed());
92 } else {
93 primary_right_items.push(right_item.boxed());
94 }
95 }
96 ToolbarItemLocation::Secondary => {
97 secondary_item = Some(
98 ChildView::new(item.as_any(), cx)
99 .constrained()
100 .with_height(theme.height)
101 .boxed(),
102 );
103 }
104 }
105 }
106
107 let pane = self.pane.clone();
108 let mut enable_go_backward = false;
109 let mut enable_go_forward = false;
110 if let Some(pane) = pane.upgrade(cx) {
111 let pane = pane.read(cx);
112 enable_go_backward = pane.can_navigate_backward();
113 enable_go_forward = pane.can_navigate_forward();
114 }
115
116 let container_style = theme.container;
117 let height = theme.height;
118 let button_style = theme.nav_button;
119 let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
120
121 Flex::column()
122 .with_child(
123 Flex::row()
124 .with_child(nav_button(
125 "icons/arrow_left_16.svg",
126 button_style,
127 tooltip_style.clone(),
128 enable_go_backward,
129 spacing,
130 super::GoBack {
131 pane: Some(pane.clone()),
132 },
133 super::GoBack { pane: None },
134 "Go Back",
135 cx,
136 ))
137 .with_child(nav_button(
138 "icons/arrow_right_16.svg",
139 button_style,
140 tooltip_style,
141 enable_go_forward,
142 spacing,
143 super::GoForward { pane: Some(pane) },
144 super::GoForward { pane: None },
145 "Go Forward",
146 cx,
147 ))
148 .with_children(primary_left_items)
149 .with_children(primary_right_items)
150 .constrained()
151 .with_height(height)
152 .boxed(),
153 )
154 .with_children(secondary_item)
155 .contained()
156 .with_style(container_style)
157 .boxed()
158 }
159}
160
161#[allow(clippy::too_many_arguments)]
162fn nav_button<A: Action + Clone>(
163 svg_path: &'static str,
164 style: theme::Interactive<theme::IconButton>,
165 tooltip_style: TooltipStyle,
166 enabled: bool,
167 spacing: f32,
168 action: A,
169 tooltip_action: A,
170 action_name: &str,
171 cx: &mut RenderContext<Toolbar>,
172) -> ElementBox {
173 MouseEventHandler::<A>::new(0, cx, |state, _| {
174 let style = if enabled {
175 style.style_for(state, false)
176 } else {
177 style.disabled_style()
178 };
179 Svg::new(svg_path)
180 .with_color(style.color)
181 .constrained()
182 .with_width(style.icon_width)
183 .aligned()
184 .contained()
185 .with_style(style.container)
186 .constrained()
187 .with_width(style.button_width)
188 .with_height(style.button_width)
189 .aligned()
190 .boxed()
191 })
192 .with_cursor_style(if enabled {
193 CursorStyle::PointingHand
194 } else {
195 CursorStyle::default()
196 })
197 .on_click(MouseButton::Left, move |_, cx| {
198 cx.dispatch_action(action.clone())
199 })
200 .with_tooltip::<A, _>(
201 0,
202 action_name.to_string(),
203 Some(Box::new(tooltip_action)),
204 tooltip_style,
205 cx,
206 )
207 .contained()
208 .with_margin_right(spacing)
209 .boxed()
210}
211
212impl Toolbar {
213 pub fn new(pane: WeakViewHandle<Pane>) -> Self {
214 Self {
215 active_pane_item: None,
216 pane,
217 items: Default::default(),
218 hidden: false,
219 }
220 }
221
222 pub fn add_item<T>(&mut self, item: ViewHandle<T>, cx: &mut ViewContext<Self>)
223 where
224 T: 'static + ToolbarItemView,
225 {
226 let location = item.set_active_pane_item(self.active_pane_item.as_deref(), cx);
227 cx.subscribe(&item, |this, item, event, cx| {
228 if let Some((_, current_location)) =
229 this.items.iter_mut().find(|(i, _)| i.id() == item.id())
230 {
231 let new_location = item
232 .read(cx)
233 .location_for_event(event, *current_location, cx);
234 if new_location != *current_location {
235 *current_location = new_location;
236 cx.notify();
237 }
238 }
239 })
240 .detach();
241 self.items.push((Box::new(item), location));
242 cx.notify();
243 }
244
245 pub fn set_active_pane_item(
246 &mut self,
247 pane_item: Option<&dyn ItemHandle>,
248 cx: &mut ViewContext<Self>,
249 ) {
250 self.active_pane_item = pane_item.map(|item| item.boxed_clone());
251 self.hidden = self
252 .active_pane_item
253 .as_ref()
254 .map(|item| !item.show_toolbar(cx))
255 .unwrap_or(false);
256
257 for (toolbar_item, current_location) in self.items.iter_mut() {
258 let new_location = toolbar_item.set_active_pane_item(pane_item, cx);
259 if new_location != *current_location {
260 *current_location = new_location;
261 cx.notify();
262 }
263 }
264 }
265
266 pub fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut AppContext) {
267 for (toolbar_item, _) in self.items.iter_mut() {
268 toolbar_item.pane_focus_update(pane_focused, cx);
269 }
270 }
271
272 pub fn item_of_type<T: ToolbarItemView>(&self) -> Option<ViewHandle<T>> {
273 self.items
274 .iter()
275 .find_map(|(item, _)| item.as_any().clone().downcast())
276 }
277
278 pub fn hidden(&self) -> bool {
279 self.hidden
280 }
281}
282
283impl<T: ToolbarItemView> ToolbarItemViewHandle for ViewHandle<T> {
284 fn id(&self) -> usize {
285 self.id()
286 }
287
288 fn as_any(&self) -> &AnyViewHandle {
289 self
290 }
291
292 fn set_active_pane_item(
293 &self,
294 active_pane_item: Option<&dyn ItemHandle>,
295 cx: &mut AppContext,
296 ) -> ToolbarItemLocation {
297 self.update(cx, |this, cx| {
298 this.set_active_pane_item(active_pane_item, cx)
299 })
300 }
301
302 fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut AppContext) {
303 self.update(cx, |this, cx| this.pane_focus_update(pane_focused, cx));
304 }
305}
306
307impl From<&dyn ToolbarItemViewHandle> for AnyViewHandle {
308 fn from(val: &dyn ToolbarItemViewHandle) -> Self {
309 val.as_any().clone()
310 }
311}