From bcf449d3fecbadcb41228e47039b5a53626d9d7b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 22 Nov 2023 15:19:10 -0800 Subject: [PATCH] Add a basic context menu to the project panel --- crates/project_panel2/src/project_panel.rs | 175 ++++++++++++--------- crates/ui2/src/components/context_menu.rs | 8 +- 2 files changed, 103 insertions(+), 80 deletions(-) diff --git a/crates/project_panel2/src/project_panel.rs b/crates/project_panel2/src/project_panel.rs index d4c63e75bf68057bffa8f616f47fa8488e28bedf..2ee2e3ab1fe52ad2e4e234ca100def9f96205340 100644 --- a/crates/project_panel2/src/project_panel.rs +++ b/crates/project_panel2/src/project_panel.rs @@ -8,10 +8,11 @@ use file_associations::FileAssociations; use anyhow::{anyhow, Result}; use gpui::{ - actions, div, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext, + actions, div, overlay, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext, ClipboardItem, Div, EventEmitter, FocusHandle, Focusable, FocusableView, InteractiveElement, - Model, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, Stateful, Styled, - Task, UniformListScrollHandle, View, ViewContext, VisualContext as _, WeakView, WindowContext, + Model, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, + Stateful, Styled, Subscription, Task, UniformListScrollHandle, View, ViewContext, + VisualContext as _, WeakView, WindowContext, }; use menu::{Confirm, SelectNext, SelectPrev}; use project::{ @@ -29,7 +30,7 @@ use std::{ sync::Arc, }; use theme::ActiveTheme as _; -use ui::{v_stack, IconElement, Label, ListItem}; +use ui::{v_stack, ContextMenu, IconElement, Label, ListItem}; use unicase::UniCase; use util::{maybe, ResultExt, TryFutureExt}; use workspace::{ @@ -49,6 +50,7 @@ pub struct ProjectPanel { last_worktree_root_id: Option, expanded_dir_ids: HashMap>, selection: Option, + context_menu: Option<(View, Point, Subscription)>, edit_state: Option, filename_editor: View, clipboard_entry: Option, @@ -231,6 +233,7 @@ impl ProjectPanel { expanded_dir_ids: Default::default(), selection: None, edit_state: None, + context_menu: None, filename_editor, clipboard_entry: None, // context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)), @@ -366,80 +369,92 @@ impl ProjectPanel { fn deploy_context_menu( &mut self, - _position: Point, - _entry_id: ProjectEntryId, - _cx: &mut ViewContext, + position: Point, + entry_id: ProjectEntryId, + cx: &mut ViewContext, ) { - // todo!() - // let project = self.project.read(cx); + let this = cx.view().clone(); + let project = self.project.read(cx); - // let worktree_id = if let Some(id) = project.worktree_id_for_entry(entry_id, cx) { - // id - // } else { - // return; - // }; + let worktree_id = if let Some(id) = project.worktree_id_for_entry(entry_id, cx) { + id + } else { + return; + }; - // self.selection = Some(Selection { - // worktree_id, - // entry_id, - // }); - - // let mut menu_entries = Vec::new(); - // if let Some((worktree, entry)) = self.selected_entry(cx) { - // let is_root = Some(entry) == worktree.root_entry(); - // if !project.is_remote() { - // menu_entries.push(ContextMenuItem::action( - // "Add Folder to Project", - // workspace::AddFolderToProject, - // )); - // if is_root { - // let project = self.project.clone(); - // menu_entries.push(ContextMenuItem::handler("Remove from Project", move |cx| { - // project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx)); - // })); - // } - // } - // menu_entries.push(ContextMenuItem::action("New File", NewFile)); - // menu_entries.push(ContextMenuItem::action("New Folder", NewDirectory)); - // menu_entries.push(ContextMenuItem::Separator); - // menu_entries.push(ContextMenuItem::action("Cut", Cut)); - // menu_entries.push(ContextMenuItem::action("Copy", Copy)); - // if let Some(clipboard_entry) = self.clipboard_entry { - // if clipboard_entry.worktree_id() == worktree.id() { - // menu_entries.push(ContextMenuItem::action("Paste", Paste)); - // } - // } - // menu_entries.push(ContextMenuItem::Separator); - // menu_entries.push(ContextMenuItem::action("Copy Path", CopyPath)); - // menu_entries.push(ContextMenuItem::action( - // "Copy Relative Path", - // CopyRelativePath, - // )); - - // if entry.is_dir() { - // menu_entries.push(ContextMenuItem::Separator); - // } - // menu_entries.push(ContextMenuItem::action("Reveal in Finder", RevealInFinder)); - // if entry.is_dir() { - // menu_entries.push(ContextMenuItem::action("Open in Terminal", OpenInTerminal)); - // menu_entries.push(ContextMenuItem::action( - // "Search Inside", - // NewSearchInDirectory, - // )); - // } - - // menu_entries.push(ContextMenuItem::Separator); - // menu_entries.push(ContextMenuItem::action("Rename", Rename)); - // if !is_root { - // menu_entries.push(ContextMenuItem::action("Delete", Delete)); - // } - // } - - // // self.context_menu.update(cx, |menu, cx| { - // // menu.show(position, AnchorCorner::TopLeft, menu_entries, cx); - // // }); - - // cx.notify(); + self.selection = Some(Selection { + worktree_id, + entry_id, + }); + + if let Some((worktree, entry)) = self.selected_entry(cx) { + let is_root = Some(entry) == worktree.root_entry(); + let is_dir = entry.is_dir(); + let worktree_id = worktree.id(); + let is_local = project.is_local(); + + let context_menu = ContextMenu::build(cx, |mut menu, cx| { + if is_local { + menu = menu.action( + "Add Folder to Project", + Box::new(workspace::AddFolderToProject), + ); + if is_root { + menu = menu.entry( + "Remove from Project", + cx.listener_for(&this, move |this, _, cx| { + this.project.update(cx, |project, cx| { + project.remove_worktree(worktree_id, cx) + }); + }), + ); + } + } + + menu = menu + .action("New File", Box::new(NewFile)) + .action("New Folder", Box::new(NewDirectory)) + .separator() + .action("Cut", Box::new(Cut)) + .action("Copy", Box::new(Copy)); + + if let Some(clipboard_entry) = self.clipboard_entry { + if clipboard_entry.worktree_id() == worktree_id { + menu = menu.action("Paste", Box::new(Paste)); + } + } + + menu = menu + .separator() + .action("Copy Path", Box::new(CopyPath)) + .action("Copy Relative Path", Box::new(CopyRelativePath)) + .separator() + .action("Reveal in Finder", Box::new(RevealInFinder)); + + if is_dir { + menu = menu + .action("Open in Terminal", Box::new(OpenInTerminal)) + .action("Search Inside", Box::new(NewSearchInDirectory)) + } + + menu = menu.separator().action("Rename", Box::new(Rename)); + + if !is_root { + menu = menu.action("Delete", Box::new(Delete)); + } + + menu + }); + + cx.focus_view(&context_menu); + let subscription = cx.on_blur(&context_menu.focus_handle(cx), |this, cx| { + this.context_menu.take(); + cx.notify(); + }); + self.context_menu = Some((context_menu, position, subscription)); + } + + cx.notify(); } fn expand_selected_entry(&mut self, _: &ExpandSelectedEntry, cx: &mut ViewContext) { @@ -1379,6 +1394,9 @@ impl ProjectPanel { .ml_1(), ) .on_click(cx.listener(move |this, event: &gpui::ClickEvent, cx| { + if event.down.button == MouseButton::Right { + return; + } if !show_editor { if kind.is_dir() { this.toggle_expanded(entry_id, cx); @@ -1415,6 +1433,7 @@ impl Render for ProjectPanel { div() .id("project-panel") .size_full() + .relative() .key_context("ProjectPanel") .on_action(cx.listener(Self::select_next)) .on_action(cx.listener(Self::select_prev)) @@ -1458,6 +1477,12 @@ impl Render for ProjectPanel { .size_full() .track_scroll(self.list.clone()), ) + .children(self.context_menu.as_ref().map(|(menu, position, _)| { + overlay() + .position(*position) + .anchor(gpui::AnchorCorner::BottomLeft) + .child(menu.clone()) + })) } else { v_stack() .id("empty-project_panel") diff --git a/crates/ui2/src/components/context_menu.rs b/crates/ui2/src/components/context_menu.rs index 8cd519f62916dd351f2056100acb8d396801f8c0..3510256c0ce50b2c73d8b091a06e433be42f8ff7 100644 --- a/crates/ui2/src/components/context_menu.rs +++ b/crates/ui2/src/components/context_menu.rs @@ -1,13 +1,10 @@ -use std::cell::RefCell; -use std::rc::Rc; - -use crate::{prelude::*, v_stack, Label, List}; -use crate::{ListItem, ListSeparator, ListSubHeader}; +use crate::{prelude::*, v_stack, Label, List, ListItem, ListSeparator, ListSubHeader}; use gpui::{ overlay, px, Action, AnchorCorner, AnyElement, AppContext, Bounds, ClickEvent, DismissEvent, DispatchPhase, Div, EventEmitter, FocusHandle, FocusableView, IntoElement, LayoutId, ManagedView, MouseButton, MouseDownEvent, Pixels, Point, Render, View, VisualContext, }; +use std::{cell::RefCell, rc::Rc}; pub enum ContextMenuItem { Separator, @@ -177,6 +174,7 @@ pub struct MenuHandleState { child_element: Option, menu_element: Option, } + impl Element for MenuHandle { type State = MenuHandleState;