Pane context menus & focus shenanigans

Co-Authored-By: Conrad Irwin <conrad@zed.dev>
This commit is contained in:
Julia 2023-11-30 14:01:09 -05:00
parent 21dfe58ad9
commit d516ae0d8a
3 changed files with 176 additions and 133 deletions

View File

@ -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| {

View File

@ -290,7 +290,6 @@ impl<T: ToolbarItemView> ToolbarItemViewHandle for View<T> {
}
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();

View File

@ -3587,87 +3587,6 @@ fn open_items(
})
}
// todo!()
// fn notify_of_new_dock(workspace: &WeakView<Workspace>, 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::<MessageNotification>(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::<MessageNotification>(MESSAGE_ID, cx)
// })
// .unwrap_or(false)
// {
// cx.update(|cx| {
// cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
// let entry = tracker
// .entry(TypeId::of::<MessageNotification>())
// .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::<ThemeSettings>(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<Workspace>, 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";