diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index 2d8bf87df08e645e212dd7331538b0fe7cbf3551..d08a5170c1ccd4d36a7006ef03d0b6708505cea8 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -2,14 +2,15 @@ use crate::{ item::{Item, ItemHandle, ItemSettings, WeakItemHandle}, toolbar::Toolbar, workspace_settings::{AutosaveSetting, WorkspaceSettings}, - SplitDirection, Workspace, + NewCenterTerminal, NewFile, NewSearch, SplitDirection, ToggleZoom, Workspace, }; use anyhow::Result; use collections::{HashMap, HashSet, VecDeque}; use gpui::{ - actions, prelude::*, Action, AnyWeakView, AppContext, AsyncWindowContext, Div, EntityId, - EventEmitter, FocusHandle, Focusable, FocusableView, Model, Pixels, Point, PromptLevel, Render, - Task, View, ViewContext, VisualContext, WeakView, WindowContext, + actions, overlay, prelude::*, Action, AnchorCorner, AnyWeakView, AppContext, + AsyncWindowContext, DismissEvent, Div, EntityId, EventEmitter, FocusHandle, Focusable, + FocusableView, Model, Pixels, Point, PromptLevel, Render, Task, View, ViewContext, + VisualContext, WeakView, WindowContext, }; use parking_lot::Mutex; use project2::{Project, ProjectEntryId, ProjectPath}; @@ -25,8 +26,8 @@ use std::{ }, }; -use ui::v_stack; -use ui::{prelude::*, Color, Icon, IconButton, IconElement, Tooltip}; +use ui::{menu_handle, prelude::*, Color, Icon, IconButton, IconElement, Tooltip}; +use ui::{v_stack, ContextMenu}; use util::truncate_and_remove_front; #[derive(PartialEq, Clone, Copy, Deserialize, Debug)] @@ -50,7 +51,7 @@ pub enum SaveIntent { //todo!("Do we need the default bound on actions? Decide soon") // #[register_action] -#[derive(Clone, Deserialize, PartialEq, Debug)] +#[derive(Action, Clone, Deserialize, PartialEq, Debug)] pub struct ActivateItem(pub usize); // #[derive(Clone, PartialEq)] @@ -158,7 +159,9 @@ pub struct Pane { autoscroll: bool, nav_history: NavHistory, toolbar: View, - // tab_bar_context_menu: TabBarContextMenu, + tab_bar_focus_handle: FocusHandle, + new_item_menu: Option>, + split_item_menu: Option>, // tab_context_menu: ViewHandle, workspace: WeakView, project: Model, @@ -323,6 +326,9 @@ impl Pane { next_timestamp, }))), toolbar: cx.build_view(|_| Toolbar::new()), + tab_bar_focus_handle: cx.focus_handle(), + new_item_menu: None, + split_item_menu: None, // tab_bar_context_menu: TabBarContextMenu { // kind: TabBarContextMenuKind::New, // handle: context_menu, @@ -397,6 +403,7 @@ impl Pane { } pub fn has_focus(&self, cx: &WindowContext) -> bool { + // todo!(); // inline this manually self.focus_handle.contains_focused(cx) } @@ -422,11 +429,11 @@ impl Pane { } active_item.focus_handle(cx).focus(cx); - // todo!() Do this once we have tab bar context menu - // } else if !self.tab_bar_context_menu.handle.is_focused() { - } else if let Some(focused) = cx.focused() { - self.last_focused_view_by_item - .insert(active_item.item_id(), focused); + } else if !self.tab_bar_focus_handle.contains_focused(cx) { + if let Some(focused) = cx.focused() { + self.last_focused_view_by_item + .insert(active_item.item_id(), focused); + } } } } @@ -673,21 +680,16 @@ impl Pane { .position(|i| i.item_id() == item.item_id()) } - // pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { - // // Potentially warn the user of the new keybinding - // let workspace_handle = self.workspace().clone(); - // cx.spawn(|_, mut cx| async move { notify_of_new_dock(&workspace_handle, &mut cx) }) - // .detach(); - - // if self.zoomed { - // cx.emit(Event::ZoomOut); - // } else if !self.items.is_empty() { - // if !self.has_focus { - // cx.focus_self(); - // } - // cx.emit(Event::ZoomIn); - // } - // } + pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { + if self.zoomed { + cx.emit(Event::ZoomOut); + } else if !self.items.is_empty() { + if !self.focus_handle.contains_focused(cx) { + cx.focus_self(); + } + cx.emit(Event::ZoomIn); + } + } pub fn activate_item( &mut self, @@ -1424,7 +1426,7 @@ impl Pane { let close_right = ItemSettings::get_global(cx).close_position.right(); let is_active = ix == self.active_item_index; - div() + let tab = div() .group("") .id(ix) .cursor_pointer() @@ -1498,13 +1500,41 @@ impl Pane { .children((!close_right).then(|| close_icon())) .child(label) .children(close_right.then(|| close_icon())), - ) + ); + + menu_handle(ix).child(|_| tab).menu(|cx| { + ContextMenu::build(cx, |menu, cx| { + menu.action( + "Close Active Item", + CloseActiveItem { save_intent: None }.boxed_clone(), + cx, + ) + .action("Close Inactive Items", CloseInactiveItems.boxed_clone(), cx) + .action("Close Clean Items", CloseCleanItems.boxed_clone(), cx) + .action( + "Close Items To The Left", + CloseItemsToTheLeft.boxed_clone(), + cx, + ) + .action( + "Close Items To The Right", + CloseItemsToTheRight.boxed_clone(), + cx, + ) + .action( + "Close All Items", + CloseAllItems { save_intent: None }.boxed_clone(), + cx, + ) + }) + }) } fn render_tab_bar(&mut self, cx: &mut ViewContext<'_, Pane>) -> impl IntoElement { div() .group("tab_bar") .id("tab_bar") + .track_focus(&self.tab_bar_focus_handle) .w_full() .flex() .bg(cx.theme().colors().tab_bar_background) @@ -1563,20 +1593,87 @@ impl Pane { .gap_px() .child( div() + .bg(gpui::blue()) .border() .border_color(gpui::red()) - .child(IconButton::new("plus", Icon::Plus)), + .child(IconButton::new("plus", Icon::Plus).on_click( + cx.listener(|this, _, cx| { + let menu = ContextMenu::build(cx, |menu, cx| { + menu.action("New File", NewFile.boxed_clone(), cx) + .action( + "New Terminal", + NewCenterTerminal.boxed_clone(), + cx, + ) + .action( + "New Search", + NewSearch.boxed_clone(), + cx, + ) + }); + cx.subscribe( + &menu, + |this, _, event: &DismissEvent, cx| { + this.focus(cx); + this.new_item_menu = None; + }, + ) + .detach(); + this.new_item_menu = Some(menu); + }), + )) + .when_some(self.new_item_menu.as_ref(), |el, new_item_menu| { + el.child(Self::render_menu_overlay(new_item_menu)) + }), ) .child( div() .border() .border_color(gpui::red()) - .child(IconButton::new("split", Icon::Split)), + .child(IconButton::new("split", Icon::Split).on_click( + cx.listener(|this, _, cx| { + let menu = ContextMenu::build(cx, |menu, cx| { + menu.action( + "Split Right", + SplitRight.boxed_clone(), + cx, + ) + .action("Split Left", SplitLeft.boxed_clone(), cx) + .action("Split Up", SplitUp.boxed_clone(), cx) + .action("Split Down", SplitDown.boxed_clone(), cx) + }); + cx.subscribe( + &menu, + |this, _, event: &DismissEvent, cx| { + this.focus(cx); + this.split_item_menu = None; + }, + ) + .detach(); + this.split_item_menu = Some(menu); + }), + )) + .when_some( + self.split_item_menu.as_ref(), + |el, split_item_menu| { + el.child(Self::render_menu_overlay(split_item_menu)) + }, + ), ), ), ) } + fn render_menu_overlay(menu: &View) -> Div { + div() + .absolute() + .z_index(1) + .bottom_0() + .right_0() + .size_0() + .child(overlay().anchor(AnchorCorner::TopRight).child(menu.clone())) + } + // fn render_tabs(&mut self, cx: &mut ViewContext) -> impl Element { // let theme = theme::current(cx).clone(); @@ -2004,25 +2101,53 @@ impl Render for Pane { .on_action(cx.listener(|pane: &mut Pane, _: &SplitDown, cx| { pane.split(SplitDirection::Down, cx) })) - // cx.add_action(Pane::toggle_zoom); - // cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| { - // pane.activate_item(action.0, true, true, cx); - // }); - // cx.add_action(|pane: &mut Pane, _: &ActivateLastItem, cx| { - // pane.activate_item(pane.items.len() - 1, true, true, cx); - // }); - // cx.add_action(|pane: &mut Pane, _: &ActivatePrevItem, cx| { - // pane.activate_prev_item(true, cx); - // }); - // cx.add_action(|pane: &mut Pane, _: &ActivateNextItem, cx| { - // pane.activate_next_item(true, cx); - // }); - // cx.add_async_action(Pane::close_active_item); - // cx.add_async_action(Pane::close_inactive_items); - // cx.add_async_action(Pane::close_clean_items); - // cx.add_async_action(Pane::close_items_to_the_left); - // cx.add_async_action(Pane::close_items_to_the_right); - // cx.add_async_action(Pane::close_all_items); + .on_action(cx.listener(Pane::toggle_zoom)) + .on_action(cx.listener(|pane: &mut Pane, action: &ActivateItem, cx| { + pane.activate_item(action.0, true, true, cx); + })) + .on_action(cx.listener(|pane: &mut Pane, _: &ActivateLastItem, cx| { + pane.activate_item(pane.items.len() - 1, true, true, cx); + })) + .on_action(cx.listener(|pane: &mut Pane, _: &ActivatePrevItem, cx| { + pane.activate_prev_item(true, cx); + })) + .on_action(cx.listener(|pane: &mut Pane, _: &ActivateNextItem, cx| { + pane.activate_next_item(true, cx); + })) + .on_action( + cx.listener(|pane: &mut Self, action: &CloseActiveItem, cx| { + pane.close_active_item(action, cx) + .map(|task| task.detach_and_log_err(cx)); + }), + ) + .on_action( + cx.listener(|pane: &mut Self, action: &CloseInactiveItems, cx| { + pane.close_inactive_items(action, cx) + .map(|task| task.detach_and_log_err(cx)); + }), + ) + .on_action( + cx.listener(|pane: &mut Self, action: &CloseCleanItems, cx| { + pane.close_clean_items(action, cx) + .map(|task| task.detach_and_log_err(cx)); + }), + ) + .on_action( + cx.listener(|pane: &mut Self, action: &CloseItemsToTheLeft, cx| { + pane.close_items_to_the_left(action, cx) + .map(|task| task.detach_and_log_err(cx)); + }), + ) + .on_action( + cx.listener(|pane: &mut Self, action: &CloseItemsToTheRight, cx| { + pane.close_items_to_the_right(action, cx) + .map(|task| task.detach_and_log_err(cx)); + }), + ) + .on_action(cx.listener(|pane: &mut Self, action: &CloseAllItems, cx| { + pane.close_all_items(action, cx) + .map(|task| task.detach_and_log_err(cx)); + })) .size_full() .on_action( cx.listener(|pane: &mut Self, action: &CloseActiveItem, cx| { diff --git a/crates/workspace2/src/toolbar.rs b/crates/workspace2/src/toolbar.rs index 7f156be0f05664023512f538cd73a5b1ce52bb4d..cefd56c93cb318f4917965c2c5edfd0a9a308c15 100644 --- a/crates/workspace2/src/toolbar.rs +++ b/crates/workspace2/src/toolbar.rs @@ -290,7 +290,6 @@ impl ToolbarItemViewHandle for View { } fn focus_changed(&mut self, pane_focused: bool, cx: &mut WindowContext) { - println!("focus changed, pane_focused: {pane_focused}"); self.update(cx, |this, cx| { this.pane_focus_update(pane_focused, cx); cx.notify(); diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index cbd3e4309cd9a3e3aa38a1dd6645cc3b22a05505..cfecf6f57b27662766f48d0d49c8e847602e7fb6 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -3587,87 +3587,6 @@ fn open_items( }) } -// todo!() -// fn notify_of_new_dock(workspace: &WeakView, cx: &mut AsyncAppContext) { -// const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system"; -// const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key"; -// const MESSAGE_ID: usize = 2; - -// if workspace -// .read_with(cx, |workspace, cx| { -// workspace.has_shown_notification_once::(MESSAGE_ID, cx) -// }) -// .unwrap_or(false) -// { -// return; -// } - -// if db::kvp::KEY_VALUE_STORE -// .read_kvp(NEW_DOCK_HINT_KEY) -// .ok() -// .flatten() -// .is_some() -// { -// if !workspace -// .read_with(cx, |workspace, cx| { -// workspace.has_shown_notification_once::(MESSAGE_ID, cx) -// }) -// .unwrap_or(false) -// { -// cx.update(|cx| { -// cx.update_global::(|tracker, _| { -// let entry = tracker -// .entry(TypeId::of::()) -// .or_default(); -// if !entry.contains(&MESSAGE_ID) { -// entry.push(MESSAGE_ID); -// } -// }); -// }); -// } - -// return; -// } - -// cx.spawn(|_| async move { -// db::kvp::KEY_VALUE_STORE -// .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string()) -// .await -// .ok(); -// }) -// .detach(); - -// workspace -// .update(cx, |workspace, cx| { -// workspace.show_notification_once(2, cx, |cx| { -// cx.build_view(|_| { -// MessageNotification::new_element(|text, _| { -// Text::new( -// "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.", -// text, -// ) -// .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| { -// let code_span_background_color = settings::get::(cx) -// .theme -// .editor -// .document_highlight_read_background; - -// cx.scene().push_quad(gpui::Quad { -// bounds, -// background: Some(code_span_background_color), -// border: Default::default(), -// corner_radii: (2.0).into(), -// }) -// }) -// .into_any() -// }) -// .with_click_message("Read more about the new panel system") -// .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST)) -// }) -// }) -// }) -// .ok(); - fn notify_if_database_failed(workspace: WindowHandle, cx: &mut AsyncAppContext) { const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";