toolbar.rs

  1use crate::{ItemHandle, Pane};
  2use gpui::{
  3    elements::*, platform::CursorStyle, Action, AnyViewHandle, AppContext, ElementBox, Entity,
  4    MouseButton, MutableAppContext, 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 MutableAppContext) {}
 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 MutableAppContext,
 34    ) -> ToolbarItemLocation;
 35    fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut MutableAppContext);
 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 MutableAppContext) {
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 MutableAppContext,
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 MutableAppContext) {
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}