zed/crates/workspace/src/toolbar.rs
Max Brunsfeld 3eee282a6b Overhaul search bar layout
* Use a single row, instead of centering the search bar within a double-row toolbar.
* Search query controls on the left, navigation on the right
* Semantic is the final mode, for greater stability between buffer and project search.
* Prevent query editor from moving when toggling path filters
2023-08-28 14:20:09 -07:00

301 lines
9.3 KiB
Rust

use crate::ItemHandle;
use gpui::{
elements::*, AnyElement, AnyViewHandle, AppContext, Entity, View, ViewContext, ViewHandle,
WindowContext,
};
pub trait ToolbarItemView: View {
fn set_active_pane_item(
&mut self,
active_pane_item: Option<&dyn crate::ItemHandle>,
cx: &mut ViewContext<Self>,
) -> 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<Self>) {}
/// 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, _cx: &ViewContext<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 focus_changed(&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_item: Option<Box<dyn ItemHandle>>,
hidden: bool,
can_navigate: bool,
items: Vec<(Box<dyn ToolbarItemViewHandle>, ToolbarItemLocation)>,
}
impl Entity for Toolbar {
type Event = ();
}
impl View for Toolbar {
fn ui_name() -> &'static str {
"Toolbar"
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
let theme = &theme::current(cx).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();
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().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 container_style = theme.container;
let height = theme.height * primary_items_row_count as f32;
let mut primary_items = Flex::row().with_spacing(spacing);
primary_items.extend(primary_left_items);
primary_items.extend(primary_right_items);
let mut toolbar = Flex::column();
if !primary_items.is_empty() {
toolbar.add_child(primary_items.constrained().with_height(height));
}
if let Some(secondary_item) = secondary_item {
toolbar.add_child(secondary_item);
}
if toolbar.is_empty() {
toolbar.into_any_named("toolbar")
} else {
toolbar
.contained()
.with_style(container_style)
.into_any_named("toolbar")
}
}
}
// <<<<<<< HEAD
// =======
// #[allow(clippy::too_many_arguments)]
// fn nav_button<A: Action, F: 'static + Fn(&mut Toolbar, &mut ViewContext<Toolbar>)>(
// svg_path: &'static str,
// style: theme::Interactive<theme::IconButton>,
// nav_button_height: f32,
// tooltip_style: TooltipStyle,
// enabled: bool,
// spacing: f32,
// on_click: F,
// tooltip_action: A,
// action_name: &'static str,
// cx: &mut ViewContext<Toolbar>,
// ) -> AnyElement<Toolbar> {
// MouseEventHandler::new::<A, _>(0, cx, |state, _| {
// let style = if enabled {
// style.style_for(state)
// } 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::<A>(
// 0,
// action_name,
// Some(Box::new(tooltip_action)),
// tooltip_style,
// cx,
// )
// .contained()
// .with_margin_right(spacing)
// .into_any_named("nav button")
// }
// >>>>>>> 139cbbfd3aebd0863a7d51b0c12d748764cf0b2e
impl Toolbar {
pub fn new() -> Self {
Self {
active_item: None,
items: Default::default(),
hidden: false,
can_navigate: true,
}
}
pub fn set_can_navigate(&mut self, can_navigate: bool, cx: &mut ViewContext<Self>) {
self.can_navigate = can_navigate;
cx.notify();
}
pub fn add_item<T>(&mut self, item: ViewHandle<T>, cx: &mut ViewContext<Self>)
where
T: 'static + ToolbarItemView,
{
let location = item.set_active_pane_item(self.active_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_item(&mut self, item: Option<&dyn ItemHandle>, cx: &mut ViewContext<Self>) {
self.active_item = item.map(|item| item.boxed_clone());
self.hidden = self
.active_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(item, cx);
if new_location != *current_location {
*current_location = new_location;
cx.notify();
}
}
}
pub fn focus_changed(&mut self, focused: bool, cx: &mut ViewContext<Self>) {
for (toolbar_item, _) in self.items.iter_mut() {
toolbar_item.focus_changed(focused, cx);
}
}
pub fn item_of_type<T: ToolbarItemView>(&self) -> Option<ViewHandle<T>> {
self.items
.iter()
.find_map(|(item, _)| item.as_any().clone().downcast())
}
pub fn hidden(&self) -> bool {
self.hidden
}
}
impl<T: ToolbarItemView> ToolbarItemViewHandle for ViewHandle<T> {
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 focus_changed(&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_with(cx, |this, cx| this.row_count(cx))
}
}
impl From<&dyn ToolbarItemViewHandle> for AnyViewHandle {
fn from(val: &dyn ToolbarItemViewHandle) -> Self {
val.as_any().clone()
}
}