@@ -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<Toolbar>,
- // tab_bar_context_menu: TabBarContextMenu,
+ tab_bar_focus_handle: FocusHandle,
+ new_item_menu: Option<View<ContextMenu>>,
+ split_item_menu: Option<View<ContextMenu>>,
// tab_context_menu: ViewHandle<ContextMenu>,
workspace: WeakView<Workspace>,
project: Model<Project>,
@@ -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<Self>) {
- // // 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<Self>) {
+ 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<ContextMenu>) -> 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<Self>) -> impl Element<Self> {
// 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| {