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