toolbar.rs

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