From b110fd5fb74021e311c220a52205e4131504d257 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 24 May 2022 18:30:04 -0600 Subject: [PATCH] Render a context menu when right-clicking in project panel It doesn't currently do anything, but I managed to get it rendering in an absolutely positioned way. --- crates/auto_update/src/auto_update.rs | 2 +- crates/chat_panel/src/chat_panel.rs | 2 +- crates/contacts_panel/src/contacts_panel.rs | 14 +-- crates/contacts_panel/src/notifications.rs | 15 +-- crates/diagnostics/src/items.rs | 4 +- crates/editor/src/editor.rs | 6 +- crates/gpui/src/elements/list.rs | 24 ++-- .../gpui/src/elements/mouse_event_handler.rs | 71 ++++++++++- crates/gpui/src/elements/overlay.rs | 30 ++++- crates/gpui/src/platform/event.rs | 3 +- crates/gpui/src/platform/mac/event.rs | 1 + crates/gpui/src/presenter.rs | 9 +- crates/gpui/src/views/select.rs | 6 +- crates/picker/src/picker.rs | 2 +- crates/project_panel/src/project_panel.rs | 118 +++++++++++++----- crates/search/src/buffer_search.rs | 4 +- crates/search/src/project_search.rs | 4 +- crates/theme/src/theme.rs | 9 ++ crates/workspace/src/lsp_status.rs | 3 +- crates/workspace/src/pane.rs | 2 +- crates/workspace/src/sidebar.rs | 2 +- crates/workspace/src/workspace.rs | 4 +- styles/src/styleTree/projectPanel.ts | 16 ++- 23 files changed, 260 insertions(+), 91 deletions(-) diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 499b3ed99d579af99ec02dddadad9a6aa77dd0d6..234319bdd66090cd5dd6bb22ccc82962a23fefd8 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -270,7 +270,7 @@ impl View for AutoUpdateIndicator { ) .boxed() }) - .on_click(|_, cx| cx.dispatch_action(DismissErrorMessage)) + .on_click(|_, _, cx| cx.dispatch_action(DismissErrorMessage)) .boxed() } AutoUpdateStatus::Idle => Empty::new().boxed(), diff --git a/crates/chat_panel/src/chat_panel.rs b/crates/chat_panel/src/chat_panel.rs index 460e01c527bddca2145f5f0fbf284403fdeb952a..29c64128d1ffb5fde745666aa2acab1ee1a8e6f2 100644 --- a/crates/chat_panel/src/chat_panel.rs +++ b/crates/chat_panel/src/chat_panel.rs @@ -320,7 +320,7 @@ impl ChatPanel { .boxed() }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(move |_, cx| { + .on_click(move |_, _, cx| { let rpc = rpc.clone(); let this = this.clone(); cx.spawn(|mut cx| async move { diff --git a/crates/contacts_panel/src/contacts_panel.rs b/crates/contacts_panel/src/contacts_panel.rs index 44aa0626c59cea9a6704e1c8bef7e28920d80c4c..763772b89e60591d4894eef3258727ee92e34359 100644 --- a/crates/contacts_panel/src/contacts_panel.rs +++ b/crates/contacts_panel/src/contacts_panel.rs @@ -302,7 +302,7 @@ impl ContactsPanel { .boxed() }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(move |_, cx| cx.dispatch_action(ToggleExpanded(section))) + .on_click(move |_, _, cx| cx.dispatch_action(ToggleExpanded(section))) .boxed() } @@ -445,7 +445,7 @@ impl ContactsPanel { } else { CursorStyle::Arrow }) - .on_click(move |_, cx| { + .on_click(move |_, _, cx| { if !is_host { cx.dispatch_global_action(JoinProject { contact: contact.clone(), @@ -507,7 +507,7 @@ impl ContactsPanel { .boxed() }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(move |_, cx| { + .on_click(move |_, _, cx| { cx.dispatch_action(RespondToContactRequest { user_id, accept: false, @@ -529,7 +529,7 @@ impl ContactsPanel { .boxed() }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(move |_, cx| { + .on_click(move |_, _, cx| { cx.dispatch_action(RespondToContactRequest { user_id, accept: true, @@ -552,7 +552,7 @@ impl ContactsPanel { }) .with_padding(Padding::uniform(2.)) .with_cursor_style(CursorStyle::PointingHand) - .on_click(move |_, cx| cx.dispatch_action(RemoveContact(user_id))) + .on_click(move |_, _, cx| cx.dispatch_action(RemoveContact(user_id))) .flex_float() .boxed(), ); @@ -865,7 +865,7 @@ impl View for ContactsPanel { .boxed() }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(|_, cx| cx.dispatch_action(contact_finder::Toggle)) + .on_click(|_, _, cx| cx.dispatch_action(contact_finder::Toggle)) .boxed(), ) .constrained() @@ -913,7 +913,7 @@ impl View for ContactsPanel { }, ) .with_cursor_style(CursorStyle::PointingHand) - .on_click(move |_, cx| { + .on_click(move |_, _, cx| { cx.write_to_clipboard(ClipboardItem::new( info.url.to_string(), )); diff --git a/crates/contacts_panel/src/notifications.rs b/crates/contacts_panel/src/notifications.rs index 555d8962d3d8003eeaa11ad078625f51844b6dbb..c02fd73b8f7143bf8b45f83ace3bedff4fc799db 100644 --- a/crates/contacts_panel/src/notifications.rs +++ b/crates/contacts_panel/src/notifications.rs @@ -61,7 +61,7 @@ pub fn render_user_notification( }) .with_cursor_style(CursorStyle::PointingHand) .with_padding(Padding::uniform(5.)) - .on_click(move |_, cx| cx.dispatch_any_action(dismiss_action.boxed_clone())) + .on_click(move |_, _, cx| cx.dispatch_any_action(dismiss_action.boxed_clone())) .aligned() .constrained() .with_height( @@ -76,13 +76,10 @@ pub fn render_user_notification( .named("contact notification header"), ) .with_children(body.map(|body| { - Label::new( - body.to_string(), - theme.body_message.text.clone(), - ) - .contained() - .with_style(theme.body_message.container) - .boxed() + Label::new(body.to_string(), theme.body_message.text.clone()) + .contained() + .with_style(theme.body_message.container) + .boxed() })) .with_children(if buttons.is_empty() { None @@ -99,7 +96,7 @@ pub fn render_user_notification( .boxed() }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(move |_, cx| cx.dispatch_any_action(action.boxed_clone())) + .on_click(move |_, _, cx| cx.dispatch_any_action(action.boxed_clone())) .boxed() }, )) diff --git a/crates/diagnostics/src/items.rs b/crates/diagnostics/src/items.rs index 426f25629dd139d21e8ad10e6abb4d33e29d4063..224e5e94a781471ce71389151cc119b3c09f537e 100644 --- a/crates/diagnostics/src/items.rs +++ b/crates/diagnostics/src/items.rs @@ -159,7 +159,7 @@ impl View for DiagnosticIndicator { .boxed() }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(|_, cx| cx.dispatch_action(crate::Deploy)) + .on_click(|_, _, cx| cx.dispatch_action(crate::Deploy)) .aligned() .boxed(), ); @@ -192,7 +192,7 @@ impl View for DiagnosticIndicator { .boxed() }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(|_, cx| cx.dispatch_action(GoToNextDiagnostic)) + .on_click(|_, _, cx| cx.dispatch_action(GoToNextDiagnostic)) .boxed(), ); } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index e5a80e44f4014127cb5cb76090bfd04a1092e65b..556b7a9091228c17650c0b26fd4b55155d74f6db 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -672,7 +672,7 @@ impl CompletionsMenu { }, ) .with_cursor_style(CursorStyle::PointingHand) - .on_mouse_down(move |cx| { + .on_mouse_down(move |_, cx| { cx.dispatch_action(ConfirmCompletion { item_ix: Some(item_ix), }); @@ -800,7 +800,7 @@ impl CodeActionsMenu { .boxed() }) .with_cursor_style(CursorStyle::PointingHand) - .on_mouse_down(move |cx| { + .on_mouse_down(move |_, cx| { cx.dispatch_action(ConfirmCodeAction { item_ix: Some(item_ix), }); @@ -2590,7 +2590,7 @@ impl Editor { }) .with_cursor_style(CursorStyle::PointingHand) .with_padding(Padding::uniform(3.)) - .on_mouse_down(|cx| { + .on_mouse_down(|_, cx| { cx.dispatch_action(ToggleCodeActions { deployed_from_indicator: true, }); diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index 77d37bc3bf3b2dc7e879fc3ddcec8ae64bff283d..969fe3cb842ee04e3f5cf127136831c341933dcb 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -612,7 +612,10 @@ mod tests { }); let mut list = List::new(state.clone()); - let (size, _) = list.layout(constraint, &mut presenter.build_layout_context(false, cx)); + let (size, _) = list.layout( + constraint, + &mut presenter.build_layout_context(vec2f(100., 40.), false, cx), + ); assert_eq!(size, vec2f(100., 40.)); assert_eq!( state.0.borrow().items.summary().clone(), @@ -634,8 +637,10 @@ mod tests { true, &mut presenter.build_event_context(cx), ); - let (_, logical_scroll_top) = - list.layout(constraint, &mut presenter.build_layout_context(false, cx)); + let (_, logical_scroll_top) = list.layout( + constraint, + &mut presenter.build_layout_context(vec2f(100., 40.), false, cx), + ); assert_eq!( logical_scroll_top, ListOffset { @@ -659,8 +664,10 @@ mod tests { } ); - let (size, logical_scroll_top) = - list.layout(constraint, &mut presenter.build_layout_context(false, cx)); + let (size, logical_scroll_top) = list.layout( + constraint, + &mut presenter.build_layout_context(vec2f(100., 40.), false, cx), + ); assert_eq!(size, vec2f(100., 40.)); assert_eq!( state.0.borrow().items.summary().clone(), @@ -770,11 +777,12 @@ mod tests { } let mut list = List::new(state.clone()); + let window_size = vec2f(width, height); let (size, logical_scroll_top) = list.layout( - SizeConstraint::new(vec2f(0., 0.), vec2f(width, height)), - &mut presenter.build_layout_context(false, cx), + SizeConstraint::new(vec2f(0., 0.), window_size), + &mut presenter.build_layout_context(window_size, false, cx), ); - assert_eq!(size, vec2f(width, height)); + assert_eq!(size, window_size); last_logical_scroll_top = Some(logical_scroll_top); let state = state.0.borrow(); diff --git a/crates/gpui/src/elements/mouse_event_handler.rs b/crates/gpui/src/elements/mouse_event_handler.rs index 1ee7c6cbb5e57bf2108d36f48424fd3c9db7e058..65cb6ed61d8717ed69e636d5e17e1ab1e77b9509 100644 --- a/crates/gpui/src/elements/mouse_event_handler.rs +++ b/crates/gpui/src/elements/mouse_event_handler.rs @@ -14,9 +14,11 @@ pub struct MouseEventHandler { state: ElementStateHandle, child: ElementBox, cursor_style: Option, - mouse_down_handler: Option>, - click_handler: Option>, + mouse_down_handler: Option>, + click_handler: Option>, drag_handler: Option>, + right_mouse_down_handler: Option>, + right_click_handler: Option>, padding: Padding, } @@ -24,6 +26,7 @@ pub struct MouseEventHandler { pub struct MouseState { pub hovered: bool, pub clicked: bool, + pub right_clicked: bool, prev_drag_position: Option, } @@ -43,6 +46,8 @@ impl MouseEventHandler { mouse_down_handler: None, click_handler: None, drag_handler: None, + right_mouse_down_handler: None, + right_click_handler: None, padding: Default::default(), } } @@ -52,12 +57,18 @@ impl MouseEventHandler { self } - pub fn on_mouse_down(mut self, handler: impl FnMut(&mut EventContext) + 'static) -> Self { + pub fn on_mouse_down( + mut self, + handler: impl FnMut(Vector2F, &mut EventContext) + 'static, + ) -> Self { self.mouse_down_handler = Some(Box::new(handler)); self } - pub fn on_click(mut self, handler: impl FnMut(usize, &mut EventContext) + 'static) -> Self { + pub fn on_click( + mut self, + handler: impl FnMut(Vector2F, usize, &mut EventContext) + 'static, + ) -> Self { self.click_handler = Some(Box::new(handler)); self } @@ -67,6 +78,22 @@ impl MouseEventHandler { self } + pub fn on_right_mouse_down( + mut self, + handler: impl FnMut(Vector2F, &mut EventContext) + 'static, + ) -> Self { + self.right_mouse_down_handler = Some(Box::new(handler)); + self + } + + pub fn on_right_click( + mut self, + handler: impl FnMut(Vector2F, usize, &mut EventContext) + 'static, + ) -> Self { + self.right_click_handler = Some(Box::new(handler)); + self + } + pub fn with_padding(mut self, padding: Padding) -> Self { self.padding = padding; self @@ -120,6 +147,8 @@ impl Element for MouseEventHandler { let mouse_down_handler = self.mouse_down_handler.as_mut(); let click_handler = self.click_handler.as_mut(); let drag_handler = self.drag_handler.as_mut(); + let right_mouse_down_handler = self.right_mouse_down_handler.as_mut(); + let right_click_handler = self.right_click_handler.as_mut(); let handled_in_child = self.child.dispatch_event(event, cx); @@ -144,7 +173,7 @@ impl Element for MouseEventHandler { state.prev_drag_position = Some(*position); cx.notify(); if let Some(handler) = mouse_down_handler { - handler(cx); + handler(*position, cx); } true } else { @@ -162,7 +191,7 @@ impl Element for MouseEventHandler { cx.notify(); if let Some(handler) = click_handler { if hit_bounds.contains_point(*position) { - handler(*click_count, cx); + handler(*position, *click_count, cx); } } true @@ -184,6 +213,36 @@ impl Element for MouseEventHandler { handled_in_child } } + Event::RightMouseDown { position, .. } => { + if !handled_in_child && hit_bounds.contains_point(*position) { + state.right_clicked = true; + cx.notify(); + if let Some(handler) = right_mouse_down_handler { + handler(*position, cx); + } + true + } else { + handled_in_child + } + } + Event::RightMouseUp { + position, + click_count, + .. + } => { + if !handled_in_child && state.right_clicked { + state.right_clicked = false; + cx.notify(); + if let Some(handler) = right_click_handler { + if hit_bounds.contains_point(*position) { + handler(*position, *click_count, cx); + } + } + true + } else { + handled_in_child + } + } _ => handled_in_child, }) } diff --git a/crates/gpui/src/elements/overlay.rs b/crates/gpui/src/elements/overlay.rs index 0cac2ed86302c8e7c88ca58fa811ee116b8deff0..3d90a7554cd15a641217173974295c5b61e6e086 100644 --- a/crates/gpui/src/elements/overlay.rs +++ b/crates/gpui/src/elements/overlay.rs @@ -1,16 +1,28 @@ +use serde_json::json; + use crate::{ geometry::{rect::RectF, vector::Vector2F}, + json::ToJson, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext, SizeConstraint, }; pub struct Overlay { child: ElementBox, + abs_position: Option, } impl Overlay { pub fn new(child: ElementBox) -> Self { - Self { child } + Self { + child, + abs_position: None, + } + } + + pub fn with_abs_position(mut self, position: Vector2F) -> Self { + self.abs_position = Some(position); + self } } @@ -23,6 +35,11 @@ impl Element for Overlay { constraint: SizeConstraint, cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { + let constraint = if self.abs_position.is_some() { + SizeConstraint::new(Vector2F::zero(), cx.window_size) + } else { + constraint + }; let size = self.child.layout(constraint, cx); (Vector2F::zero(), size) } @@ -34,9 +51,10 @@ impl Element for Overlay { size: &mut Self::LayoutState, cx: &mut PaintContext, ) { - let bounds = RectF::new(bounds.origin(), *size); + let origin = self.abs_position.unwrap_or(bounds.origin()); + let visible_bounds = RectF::new(origin, *size); cx.scene.push_stacking_context(None); - self.child.paint(bounds.origin(), bounds, cx); + self.child.paint(origin, visible_bounds, cx); cx.scene.pop_stacking_context(); } @@ -59,6 +77,10 @@ impl Element for Overlay { _: &Self::PaintState, cx: &DebugContext, ) -> serde_json::Value { - self.child.debug(cx) + json!({ + "type": "Overlay", + "abs_position": self.abs_position.to_json(), + "child": self.child.debug(cx), + }) } } diff --git a/crates/gpui/src/platform/event.rs b/crates/gpui/src/platform/event.rs index b32ab952c79dfbef1b1a539bec117463954c25bf..61cfa99bfe0a8b6cc032baf0788eb0b3541f36ba 100644 --- a/crates/gpui/src/platform/event.rs +++ b/crates/gpui/src/platform/event.rs @@ -43,6 +43,7 @@ pub enum Event { }, RightMouseUp { position: Vector2F, + click_count: usize, }, NavigateMouseDown { position: Vector2F, @@ -72,7 +73,7 @@ impl Event { | Event::LeftMouseUp { position, .. } | Event::LeftMouseDragged { position } | Event::RightMouseDown { position, .. } - | Event::RightMouseUp { position } + | Event::RightMouseUp { position, .. } | Event::NavigateMouseDown { position, .. } | Event::NavigateMouseUp { position, .. } | Event::MouseMoved { position, .. } => Some(*position), diff --git a/crates/gpui/src/platform/mac/event.rs b/crates/gpui/src/platform/mac/event.rs index 9d07177b16fd322c92973745983396a6c0abf5ea..4d3aa6cf9ac8ff65c2ef92d4acb22ce70a585fe9 100644 --- a/crates/gpui/src/platform/mac/event.rs +++ b/crates/gpui/src/platform/mac/event.rs @@ -178,6 +178,7 @@ impl Event { native_event.locationInWindow().x as f32, window_height - native_event.locationInWindow().y as f32, ), + click_count: native_event.clickCount() as usize, }), NSEventType::NSOtherMouseDown => { let direction = match native_event.buttonNumber() { diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index fbdd6963e372f711d193c9bde16b34156d6118d5..2a2da34d631101d3fef73db3469fcdca6eaa6598 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -134,15 +134,16 @@ impl Presenter { scene } - fn layout(&mut self, size: Vector2F, refreshing: bool, cx: &mut MutableAppContext) { + fn layout(&mut self, window_size: Vector2F, refreshing: bool, cx: &mut MutableAppContext) { if let Some(root_view_id) = cx.root_view_id(self.window_id) { - self.build_layout_context(refreshing, cx) - .layout(root_view_id, SizeConstraint::strict(size)); + self.build_layout_context(window_size, refreshing, cx) + .layout(root_view_id, SizeConstraint::strict(window_size)); } } pub fn build_layout_context<'a>( &'a mut self, + window_size: Vector2F, refreshing: bool, cx: &'a mut MutableAppContext, ) -> LayoutContext<'a> { @@ -150,6 +151,7 @@ impl Presenter { rendered_views: &mut self.rendered_views, parents: &mut self.parents, refreshing, + window_size, font_cache: &self.font_cache, font_system: cx.platform().fonts(), text_layout_cache: &self.text_layout_cache, @@ -259,6 +261,7 @@ pub struct LayoutContext<'a> { parents: &'a mut HashMap, view_stack: Vec, pub refreshing: bool, + pub window_size: Vector2F, pub font_cache: &'a Arc, pub font_system: Arc, pub text_layout_cache: &'a TextLayoutCache, diff --git a/crates/gpui/src/views/select.rs b/crates/gpui/src/views/select.rs index d5d2105c3f34e6b74126080771118f07df8903af..a58be77c03299c195948c5ca0791179182d4e2b5 100644 --- a/crates/gpui/src/views/select.rs +++ b/crates/gpui/src/views/select.rs @@ -119,7 +119,7 @@ impl View for Select { .with_style(style.header) .boxed() }) - .on_click(move |_, cx| cx.dispatch_action(ToggleSelect)) + .on_click(move |_, _, cx| cx.dispatch_action(ToggleSelect)) .boxed(), ); if self.is_open { @@ -153,7 +153,9 @@ impl View for Select { ) }, ) - .on_click(move |_, cx| cx.dispatch_action(SelectItem(ix))) + .on_click(move |_, _, cx| { + cx.dispatch_action(SelectItem(ix)) + }) .boxed() })) }, diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index 67db36208b0b114fabb2457b9388809a1a80cee2..8fd662978b4eb3b0c334aa04a9a40ffc76b1dc21 100644 --- a/crates/picker/src/picker.rs +++ b/crates/picker/src/picker.rs @@ -90,7 +90,7 @@ impl View for Picker { .read(cx) .render_match(ix, state, ix == selected_ix, cx) }) - .on_mouse_down(move |cx| cx.dispatch_action(SelectIndex(ix))) + .on_mouse_down(move |_, cx| cx.dispatch_action(SelectIndex(ix))) .with_cursor_style(CursorStyle::PointingHand) .boxed() })); diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 7056eb9ceb61649c91282885b30b5372f5948031..f3506e2c94bb754efa5d45a958f4344af300ae12 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -4,13 +4,14 @@ use gpui::{ actions, anyhow::{anyhow, Result}, elements::{ - ChildView, ConstrainedBox, Empty, Flex, Label, MouseEventHandler, ParentElement, - ScrollTarget, Svg, UniformList, UniformListState, + ChildView, ConstrainedBox, Empty, Flex, Label, MouseEventHandler, Overlay, ParentElement, + ScrollTarget, Stack, Svg, UniformList, UniformListState, }, + geometry::vector::Vector2F, impl_internal_actions, keymap, platform::CursorStyle, - AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, PromptLevel, Task, - View, ViewContext, ViewHandle, WeakViewHandle, + AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, PromptLevel, + RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; use settings::Settings; @@ -36,6 +37,7 @@ pub struct ProjectPanel { selection: Option, edit_state: Option, filename_editor: ViewHandle, + context_menu: Option, handle: WeakViewHandle, } @@ -75,6 +77,17 @@ pub struct Open { pub change_focus: bool, } +#[derive(Clone)] +pub struct DeployContextMenu { + pub position: Vector2F, + pub entry_id: Option, +} + +pub struct ContextMenu { + pub position: Vector2F, + pub entry_id: Option, +} + actions!( project_panel, [ @@ -86,9 +99,10 @@ actions!( Rename ] ); -impl_internal_actions!(project_panel, [Open, ToggleExpanded]); +impl_internal_actions!(project_panel, [Open, ToggleExpanded, DeployContextMenu]); pub fn init(cx: &mut MutableAppContext) { + cx.add_action(ProjectPanel::deploy_context_menu); cx.add_action(ProjectPanel::expand_selected_entry); cx.add_action(ProjectPanel::collapse_selected_entry); cx.add_action(ProjectPanel::toggle_expanded); @@ -156,6 +170,7 @@ impl ProjectPanel { selection: None, edit_state: None, filename_editor, + context_menu: None, handle: cx.weak_handle(), }; this.update_visible_entries(None, cx); @@ -195,6 +210,14 @@ impl ProjectPanel { project_panel } + fn deploy_context_menu(&mut self, action: &DeployContextMenu, cx: &mut ViewContext) { + self.context_menu = Some(ContextMenu { + position: action.position, + entry_id: action.entry_id, + }); + cx.notify(); + } + fn expand_selected_entry(&mut self, _: &ExpandSelectedEntry, cx: &mut ViewContext) { if let Some((worktree, entry)) = self.selected_entry(cx) { let expanded_dir_ids = @@ -841,7 +864,7 @@ impl ProjectPanel { .with_padding_left(padding) .boxed() }) - .on_click(move |click_count, cx| { + .on_click(move |_, click_count, cx| { if kind == EntryKind::Dir { cx.dispatch_action(ToggleExpanded(entry_id)) } else { @@ -851,9 +874,33 @@ impl ProjectPanel { }) } }) + .on_right_mouse_down(move |position, cx| { + cx.dispatch_action(DeployContextMenu { + entry_id: Some(entry_id), + position, + }) + }) .with_cursor_style(CursorStyle::PointingHand) .boxed() } + + fn render_context_menu(&self, cx: &mut RenderContext) -> Option { + self.context_menu.as_ref().map(|menu| { + let style = &cx.global::().theme.project_panel.context_menu; + + Overlay::new( + Flex::column() + .with_child(Label::new("Add File".to_string(), style.label.clone()).boxed()) + .contained() + .with_style(style.container) + // .constrained() + // .with_width(style.width) + .boxed(), + ) + .with_abs_position(menu.position) + .named("Project Panel Context Menu") + }) + } } impl View for ProjectPanel { @@ -866,33 +913,38 @@ impl View for ProjectPanel { let mut container_style = theme.container; let padding = std::mem::take(&mut container_style.padding); let handle = self.handle.clone(); - UniformList::new( - self.list.clone(), - self.visible_entries - .iter() - .map(|(_, worktree_entries)| worktree_entries.len()) - .sum(), - move |range, items, cx| { - let theme = cx.global::().theme.clone(); - let this = handle.upgrade(cx).unwrap(); - this.update(cx.app, |this, cx| { - this.for_each_visible_entry(range.clone(), cx, |id, details, cx| { - items.push(Self::render_entry( - id, - details, - &this.filename_editor, - &theme.project_panel, - cx, - )); - }); - }) - }, - ) - .with_padding_top(padding.top) - .with_padding_bottom(padding.bottom) - .contained() - .with_style(container_style) - .boxed() + Stack::new() + .with_child( + UniformList::new( + self.list.clone(), + self.visible_entries + .iter() + .map(|(_, worktree_entries)| worktree_entries.len()) + .sum(), + move |range, items, cx| { + let theme = cx.global::().theme.clone(); + let this = handle.upgrade(cx).unwrap(); + this.update(cx.app, |this, cx| { + this.for_each_visible_entry(range.clone(), cx, |id, details, cx| { + items.push(Self::render_entry( + id, + details, + &this.filename_editor, + &theme.project_panel, + cx, + )); + }); + }) + }, + ) + .with_padding_top(padding.top) + .with_padding_bottom(padding.bottom) + .contained() + .with_style(container_style) + .boxed(), + ) + .with_children(self.render_context_menu(cx)) + .boxed() } fn keymap_context(&self, _: &AppContext) -> keymap::Context { diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 5581cbd60870f5cec82dad5af5e634ffa342c2f6..94b6261a0f11c3b4ba56333a22365fdf6fb3a79a 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -290,7 +290,7 @@ impl BufferSearchBar { .with_style(style.container) .boxed() }) - .on_click(move |_, cx| cx.dispatch_action(ToggleSearchOption(search_option))) + .on_click(move |_, _, cx| cx.dispatch_action(ToggleSearchOption(search_option))) .with_cursor_style(CursorStyle::PointingHand) .boxed() } @@ -314,7 +314,7 @@ impl BufferSearchBar { .with_style(style.container) .boxed() }) - .on_click(move |_, cx| match direction { + .on_click(move |_, _, cx| match direction { Direction::Prev => cx.dispatch_action(SelectPrevMatch), Direction::Next => cx.dispatch_action(SelectNextMatch), }) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 4549aa4f90388bb441bcbbf6cad428136061eccd..e3834f6f45d8e8caaee731445074875753d09a0d 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -672,7 +672,7 @@ impl ProjectSearchBar { .with_style(style.container) .boxed() }) - .on_click(move |_, cx| match direction { + .on_click(move |_, _, cx| match direction { Direction::Prev => cx.dispatch_action(SelectPrevMatch), Direction::Next => cx.dispatch_action(SelectNextMatch), }) @@ -699,7 +699,7 @@ impl ProjectSearchBar { .with_style(style.container) .boxed() }) - .on_click(move |_, cx| cx.dispatch_action(ToggleSearchOption(option))) + .on_click(move |_, _, cx| cx.dispatch_action(ToggleSearchOption(option))) .with_cursor_style(CursorStyle::PointingHand) .boxed() } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index bc9e93025d1d1a13c4d0235382a3864f91f33729..28e31ad3bbd5abf2ed06c7b5a26b441faaa6fe8c 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -226,6 +226,7 @@ pub struct ProjectPanel { pub ignored_entry_fade: f32, pub filename_editor: FieldEditor, pub indent_width: f32, + pub context_menu: ContextMenu, } #[derive(Clone, Debug, Deserialize, Default)] @@ -239,6 +240,14 @@ pub struct ProjectPanelEntry { pub icon_spacing: f32, } +#[derive(Clone, Debug, Deserialize, Default)] +pub struct ContextMenu { + pub width: f32, + #[serde(flatten)] + pub container: ContainerStyle, + pub label: TextStyle, +} + #[derive(Debug, Deserialize, Default)] pub struct CommandPalette { pub key: Interactive, diff --git a/crates/workspace/src/lsp_status.rs b/crates/workspace/src/lsp_status.rs index f58e0b973e05e27db44359b95c11524010e14ea2..ab1ae4931fecd0ebf8f09a574f29755859d53992 100644 --- a/crates/workspace/src/lsp_status.rs +++ b/crates/workspace/src/lsp_status.rs @@ -168,7 +168,8 @@ impl View for LspStatus { self.failed.join(", "), if self.failed.len() > 1 { "s" } else { "" } ); - handler = Some(|_, cx: &mut EventContext| cx.dispatch_action(DismissErrorMessage)); + handler = + Some(|_, _, cx: &mut EventContext| cx.dispatch_action(DismissErrorMessage)); } else { return Empty::new().boxed(); } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 8b97ef1a80476d5c33b4ff8dc18aaaa1ecf9bd88..ba2a21bce4cad4e0ada71ef312366afe7e0f555e 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -788,7 +788,7 @@ impl Pane { .with_cursor_style(CursorStyle::PointingHand) .on_click({ let pane = pane.clone(); - move |_, cx| { + move |_, _, cx| { cx.dispatch_action(CloseItem { item_id, pane: pane.clone(), diff --git a/crates/workspace/src/sidebar.rs b/crates/workspace/src/sidebar.rs index afdacc2a31a86f297c70e9b2a4168d01720a4da8..5aec332913420cc8beeab4a071b7a95468919d6b 100644 --- a/crates/workspace/src/sidebar.rs +++ b/crates/workspace/src/sidebar.rs @@ -293,7 +293,7 @@ impl View for SidebarButtons { .boxed() }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(move |_, cx| { + .on_click(move |_, _, cx| { cx.dispatch_action(ToggleSidebarItem { side, item_index: ix, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index e9f0efa31115dac5d98eb13826526f4dc96994ec..f4197e72962e6536afbefaf48d08be23f3338e8d 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1730,7 +1730,7 @@ impl Workspace { .with_style(style.container) .boxed() }) - .on_click(|_, cx| cx.dispatch_action(Authenticate)) + .on_click(|_, _, cx| cx.dispatch_action(Authenticate)) .with_cursor_style(CursorStyle::PointingHand) .aligned() .boxed(), @@ -1781,7 +1781,7 @@ impl Workspace { if let Some(peer_id) = peer_id { MouseEventHandler::new::(replica_id.into(), cx, move |_, _| content) .with_cursor_style(CursorStyle::PointingHand) - .on_click(move |_, cx| cx.dispatch_action(ToggleFollow(peer_id))) + .on_click(move |_, _, cx| cx.dispatch_action(ToggleFollow(peer_id))) .boxed() } else { content diff --git a/styles/src/styleTree/projectPanel.ts b/styles/src/styleTree/projectPanel.ts index 2f3e3eea72195e415a2a536bc959414f857b30b9..66aaa85952ce26010e76c9a8d0721c7fcfaaf51e 100644 --- a/styles/src/styleTree/projectPanel.ts +++ b/styles/src/styleTree/projectPanel.ts @@ -1,6 +1,6 @@ import Theme from "../themes/common/theme"; import { panel } from "./app"; -import { backgroundColor, iconColor, player, text } from "./components"; +import { backgroundColor, iconColor, player, shadow, text } from "./components"; export default function projectPanel(theme: Theme) { return { @@ -32,5 +32,19 @@ export default function projectPanel(theme: Theme) { text: text(theme, "mono", "primary", { size: "sm" }), selection: player(theme, 1).selection, }, + contextMenu: { + width: 100, + // background: "#ff0000", + background: backgroundColor(theme, 300, "base"), + cornerRadius: 6, + padding: { + bottom: 2, + left: 6, + right: 6, + top: 2, + }, + label: text(theme, "sans", "secondary", { size: "sm" }), + shadow: shadow(theme), + } }; }