use crate::{ItemHandle, Pane}; use gpui::{ elements::*, platform::CursorStyle, platform::MouseButton, Action, AnyElement, AnyViewHandle, AppContext, Entity, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; use settings::Settings; pub trait ToolbarItemView: View { fn set_active_pane_item( &mut self, active_pane_item: Option<&dyn crate::ItemHandle>, cx: &mut ViewContext, ) -> ToolbarItemLocation; fn location_for_event( &self, _event: &Self::Event, current_location: ToolbarItemLocation, _cx: &AppContext, ) -> ToolbarItemLocation { current_location } fn pane_focus_update(&mut self, _pane_focused: bool, _cx: &mut ViewContext) {} /// Number of times toolbar's height will be repeated to get the effective height. /// Useful when multiple rows one under each other are needed. /// The rows have the same width and act as a whole when reacting to resizes and similar events. fn row_count(&self) -> usize { 1 } } trait ToolbarItemViewHandle { fn id(&self) -> usize; fn as_any(&self) -> &AnyViewHandle; fn set_active_pane_item( &self, active_pane_item: Option<&dyn ItemHandle>, cx: &mut WindowContext, ) -> ToolbarItemLocation; fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut WindowContext); fn row_count(&self, cx: &WindowContext) -> usize; } #[derive(Copy, Clone, Debug, PartialEq)] pub enum ToolbarItemLocation { Hidden, PrimaryLeft { flex: Option<(f32, bool)> }, PrimaryRight { flex: Option<(f32, bool)> }, Secondary, } pub struct Toolbar { active_pane_item: Option>, hidden: bool, pane: WeakViewHandle, items: Vec<(Box, ToolbarItemLocation)>, } impl Entity for Toolbar { type Event = (); } impl View for Toolbar { fn ui_name() -> &'static str { "Toolbar" } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { let theme = &cx.global::().theme.workspace.toolbar; let mut primary_left_items = Vec::new(); let mut primary_right_items = Vec::new(); let mut secondary_item = None; let spacing = theme.item_spacing; let mut primary_items_row_count = 1; for (item, position) in &self.items { match *position { ToolbarItemLocation::Hidden => {} ToolbarItemLocation::PrimaryLeft { flex } => { primary_items_row_count = primary_items_row_count.max(item.row_count(cx)); let left_item = ChildView::new(item.as_any(), cx) .aligned() .contained() .with_margin_right(spacing); if let Some((flex, expanded)) = flex { primary_left_items.push(left_item.flex(flex, expanded).into_any()); } else { primary_left_items.push(left_item.into_any()); } } ToolbarItemLocation::PrimaryRight { flex } => { primary_items_row_count = primary_items_row_count.max(item.row_count(cx)); let right_item = ChildView::new(item.as_any(), cx) .aligned() .contained() .with_margin_left(spacing) .flex_float(); if let Some((flex, expanded)) = flex { primary_right_items.push(right_item.flex(flex, expanded).into_any()); } else { primary_right_items.push(right_item.into_any()); } } ToolbarItemLocation::Secondary => { secondary_item = Some( ChildView::new(item.as_any(), cx) .constrained() .with_height(theme.height * item.row_count(cx) as f32) .into_any(), ); } } } let pane = self.pane.clone(); let mut enable_go_backward = false; let mut enable_go_forward = false; if let Some(pane) = pane.upgrade(cx) { let pane = pane.read(cx); enable_go_backward = pane.can_navigate_backward(); enable_go_forward = pane.can_navigate_forward(); } let container_style = theme.container; let height = theme.height * primary_items_row_count as f32; let nav_button_height = theme.height; let button_style = theme.nav_button; let tooltip_style = cx.global::().theme.tooltip.clone(); Flex::column() .with_child( Flex::row() .with_child(nav_button( "icons/arrow_left_16.svg", button_style, nav_button_height, tooltip_style.clone(), enable_go_backward, spacing, { let pane = pane.clone(); move |toolbar, cx| { if let Some(workspace) = toolbar .pane .upgrade(cx) .and_then(|pane| pane.read(cx).workspace().upgrade(cx)) { let pane = pane.clone(); cx.window_context().defer(move |cx| { workspace.update(cx, |workspace, cx| { Pane::go_back(workspace, Some(pane.clone()), cx) .detach_and_log_err(cx); }); }) } } }, super::GoBack { pane: None }, "Go Back", cx, )) .with_child(nav_button( "icons/arrow_right_16.svg", button_style, nav_button_height, tooltip_style, enable_go_forward, spacing, { let pane = pane.clone(); move |toolbar, cx| { if let Some(workspace) = toolbar .pane .upgrade(cx) .and_then(|pane| pane.read(cx).workspace().upgrade(cx)) { let pane = pane.clone(); cx.window_context().defer(move |cx| { workspace.update(cx, |workspace, cx| { Pane::go_forward(workspace, Some(pane.clone()), cx) .detach_and_log_err(cx); }); }); } } }, super::GoForward { pane: None }, "Go Forward", cx, )) .with_children(primary_left_items) .with_children(primary_right_items) .constrained() .with_height(height), ) .with_children(secondary_item) .contained() .with_style(container_style) .into_any_named("toolbar") } } #[allow(clippy::too_many_arguments)] fn nav_button)>( svg_path: &'static str, style: theme::Interactive, nav_button_height: f32, tooltip_style: TooltipStyle, enabled: bool, spacing: f32, on_click: F, tooltip_action: A, action_name: &str, cx: &mut ViewContext, ) -> AnyElement { MouseEventHandler::::new(0, cx, |state, _| { let style = if enabled { style.style_for(state, false) } else { style.disabled_style() }; Svg::new(svg_path) .with_color(style.color) .constrained() .with_width(style.icon_width) .aligned() .contained() .with_style(style.container) .constrained() .with_width(style.button_width) .with_height(nav_button_height) .aligned() .top() }) .with_cursor_style(if enabled { CursorStyle::PointingHand } else { CursorStyle::default() }) .on_click(MouseButton::Left, move |_, toolbar, cx| { on_click(toolbar, cx) }) .with_tooltip::( 0, action_name.to_string(), Some(Box::new(tooltip_action)), tooltip_style, cx, ) .contained() .with_margin_right(spacing) .into_any_named("nav button") } impl Toolbar { pub fn new(pane: WeakViewHandle) -> Self { Self { active_pane_item: None, pane, items: Default::default(), hidden: false, } } pub fn add_item(&mut self, item: ViewHandle, cx: &mut ViewContext) where T: 'static + ToolbarItemView, { let location = item.set_active_pane_item(self.active_pane_item.as_deref(), cx); cx.subscribe(&item, |this, item, event, cx| { if let Some((_, current_location)) = this.items.iter_mut().find(|(i, _)| i.id() == item.id()) { let new_location = item .read(cx) .location_for_event(event, *current_location, cx); if new_location != *current_location { *current_location = new_location; cx.notify(); } } }) .detach(); self.items.push((Box::new(item), location)); cx.notify(); } pub fn set_active_pane_item( &mut self, pane_item: Option<&dyn ItemHandle>, cx: &mut ViewContext, ) { self.active_pane_item = pane_item.map(|item| item.boxed_clone()); self.hidden = self .active_pane_item .as_ref() .map(|item| !item.show_toolbar(cx)) .unwrap_or(false); for (toolbar_item, current_location) in self.items.iter_mut() { let new_location = toolbar_item.set_active_pane_item(pane_item, cx); if new_location != *current_location { *current_location = new_location; cx.notify(); } } } pub fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut ViewContext) { for (toolbar_item, _) in self.items.iter_mut() { toolbar_item.pane_focus_update(pane_focused, cx); } } pub fn item_of_type(&self) -> Option> { self.items .iter() .find_map(|(item, _)| item.as_any().clone().downcast()) } pub fn hidden(&self) -> bool { self.hidden } } impl ToolbarItemViewHandle for ViewHandle { fn id(&self) -> usize { self.id() } fn as_any(&self) -> &AnyViewHandle { self } fn set_active_pane_item( &self, active_pane_item: Option<&dyn ItemHandle>, cx: &mut WindowContext, ) -> ToolbarItemLocation { self.update(cx, |this, cx| { this.set_active_pane_item(active_pane_item, cx) }) } fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut WindowContext) { self.update(cx, |this, cx| { this.pane_focus_update(pane_focused, cx); cx.notify(); }); } fn row_count(&self, cx: &WindowContext) -> usize { self.read(cx).row_count() } } impl From<&dyn ToolbarItemViewHandle> for AnyViewHandle { fn from(val: &dyn ToolbarItemViewHandle) -> Self { val.as_any().clone() } }