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                        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}