toolbar.rs

  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}