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.
This commit is contained in:
Nathan Sobo 2022-05-24 18:30:04 -06:00 committed by Antonio Scandurra
parent f4d13ef596
commit b110fd5fb7
23 changed files with 260 additions and 91 deletions

View File

@ -270,7 +270,7 @@ impl View for AutoUpdateIndicator {
) )
.boxed() .boxed()
}) })
.on_click(|_, cx| cx.dispatch_action(DismissErrorMessage)) .on_click(|_, _, cx| cx.dispatch_action(DismissErrorMessage))
.boxed() .boxed()
} }
AutoUpdateStatus::Idle => Empty::new().boxed(), AutoUpdateStatus::Idle => Empty::new().boxed(),

View File

@ -320,7 +320,7 @@ impl ChatPanel {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(move |_, cx| { .on_click(move |_, _, cx| {
let rpc = rpc.clone(); let rpc = rpc.clone();
let this = this.clone(); let this = this.clone();
cx.spawn(|mut cx| async move { cx.spawn(|mut cx| async move {

View File

@ -302,7 +302,7 @@ impl ContactsPanel {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(move |_, cx| cx.dispatch_action(ToggleExpanded(section))) .on_click(move |_, _, cx| cx.dispatch_action(ToggleExpanded(section)))
.boxed() .boxed()
} }
@ -445,7 +445,7 @@ impl ContactsPanel {
} else { } else {
CursorStyle::Arrow CursorStyle::Arrow
}) })
.on_click(move |_, cx| { .on_click(move |_, _, cx| {
if !is_host { if !is_host {
cx.dispatch_global_action(JoinProject { cx.dispatch_global_action(JoinProject {
contact: contact.clone(), contact: contact.clone(),
@ -507,7 +507,7 @@ impl ContactsPanel {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(move |_, cx| { .on_click(move |_, _, cx| {
cx.dispatch_action(RespondToContactRequest { cx.dispatch_action(RespondToContactRequest {
user_id, user_id,
accept: false, accept: false,
@ -529,7 +529,7 @@ impl ContactsPanel {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(move |_, cx| { .on_click(move |_, _, cx| {
cx.dispatch_action(RespondToContactRequest { cx.dispatch_action(RespondToContactRequest {
user_id, user_id,
accept: true, accept: true,
@ -552,7 +552,7 @@ impl ContactsPanel {
}) })
.with_padding(Padding::uniform(2.)) .with_padding(Padding::uniform(2.))
.with_cursor_style(CursorStyle::PointingHand) .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() .flex_float()
.boxed(), .boxed(),
); );
@ -865,7 +865,7 @@ impl View for ContactsPanel {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(|_, cx| cx.dispatch_action(contact_finder::Toggle)) .on_click(|_, _, cx| cx.dispatch_action(contact_finder::Toggle))
.boxed(), .boxed(),
) )
.constrained() .constrained()
@ -913,7 +913,7 @@ impl View for ContactsPanel {
}, },
) )
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(move |_, cx| { .on_click(move |_, _, cx| {
cx.write_to_clipboard(ClipboardItem::new( cx.write_to_clipboard(ClipboardItem::new(
info.url.to_string(), info.url.to_string(),
)); ));

View File

@ -61,7 +61,7 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.with_padding(Padding::uniform(5.)) .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() .aligned()
.constrained() .constrained()
.with_height( .with_height(
@ -76,13 +76,10 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
.named("contact notification header"), .named("contact notification header"),
) )
.with_children(body.map(|body| { .with_children(body.map(|body| {
Label::new( Label::new(body.to_string(), theme.body_message.text.clone())
body.to_string(), .contained()
theme.body_message.text.clone(), .with_style(theme.body_message.container)
) .boxed()
.contained()
.with_style(theme.body_message.container)
.boxed()
})) }))
.with_children(if buttons.is_empty() { .with_children(if buttons.is_empty() {
None None
@ -99,7 +96,7 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .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() .boxed()
}, },
)) ))

View File

@ -159,7 +159,7 @@ impl View for DiagnosticIndicator {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(|_, cx| cx.dispatch_action(crate::Deploy)) .on_click(|_, _, cx| cx.dispatch_action(crate::Deploy))
.aligned() .aligned()
.boxed(), .boxed(),
); );
@ -192,7 +192,7 @@ impl View for DiagnosticIndicator {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(|_, cx| cx.dispatch_action(GoToNextDiagnostic)) .on_click(|_, _, cx| cx.dispatch_action(GoToNextDiagnostic))
.boxed(), .boxed(),
); );
} }

View File

@ -672,7 +672,7 @@ impl CompletionsMenu {
}, },
) )
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_mouse_down(move |cx| { .on_mouse_down(move |_, cx| {
cx.dispatch_action(ConfirmCompletion { cx.dispatch_action(ConfirmCompletion {
item_ix: Some(item_ix), item_ix: Some(item_ix),
}); });
@ -800,7 +800,7 @@ impl CodeActionsMenu {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_mouse_down(move |cx| { .on_mouse_down(move |_, cx| {
cx.dispatch_action(ConfirmCodeAction { cx.dispatch_action(ConfirmCodeAction {
item_ix: Some(item_ix), item_ix: Some(item_ix),
}); });
@ -2590,7 +2590,7 @@ impl Editor {
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.with_padding(Padding::uniform(3.)) .with_padding(Padding::uniform(3.))
.on_mouse_down(|cx| { .on_mouse_down(|_, cx| {
cx.dispatch_action(ToggleCodeActions { cx.dispatch_action(ToggleCodeActions {
deployed_from_indicator: true, deployed_from_indicator: true,
}); });

View File

@ -612,7 +612,10 @@ mod tests {
}); });
let mut list = List::new(state.clone()); 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!(size, vec2f(100., 40.));
assert_eq!( assert_eq!(
state.0.borrow().items.summary().clone(), state.0.borrow().items.summary().clone(),
@ -634,8 +637,10 @@ mod tests {
true, true,
&mut presenter.build_event_context(cx), &mut presenter.build_event_context(cx),
); );
let (_, logical_scroll_top) = let (_, logical_scroll_top) = list.layout(
list.layout(constraint, &mut presenter.build_layout_context(false, cx)); constraint,
&mut presenter.build_layout_context(vec2f(100., 40.), false, cx),
);
assert_eq!( assert_eq!(
logical_scroll_top, logical_scroll_top,
ListOffset { ListOffset {
@ -659,8 +664,10 @@ mod tests {
} }
); );
let (size, logical_scroll_top) = let (size, logical_scroll_top) = list.layout(
list.layout(constraint, &mut presenter.build_layout_context(false, cx)); constraint,
&mut presenter.build_layout_context(vec2f(100., 40.), false, cx),
);
assert_eq!(size, vec2f(100., 40.)); assert_eq!(size, vec2f(100., 40.));
assert_eq!( assert_eq!(
state.0.borrow().items.summary().clone(), state.0.borrow().items.summary().clone(),
@ -770,11 +777,12 @@ mod tests {
} }
let mut list = List::new(state.clone()); let mut list = List::new(state.clone());
let window_size = vec2f(width, height);
let (size, logical_scroll_top) = list.layout( let (size, logical_scroll_top) = list.layout(
SizeConstraint::new(vec2f(0., 0.), vec2f(width, height)), SizeConstraint::new(vec2f(0., 0.), window_size),
&mut presenter.build_layout_context(false, cx), &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); last_logical_scroll_top = Some(logical_scroll_top);
let state = state.0.borrow(); let state = state.0.borrow();

View File

@ -14,9 +14,11 @@ pub struct MouseEventHandler {
state: ElementStateHandle<MouseState>, state: ElementStateHandle<MouseState>,
child: ElementBox, child: ElementBox,
cursor_style: Option<CursorStyle>, cursor_style: Option<CursorStyle>,
mouse_down_handler: Option<Box<dyn FnMut(&mut EventContext)>>, mouse_down_handler: Option<Box<dyn FnMut(Vector2F, &mut EventContext)>>,
click_handler: Option<Box<dyn FnMut(usize, &mut EventContext)>>, click_handler: Option<Box<dyn FnMut(Vector2F, usize, &mut EventContext)>>,
drag_handler: Option<Box<dyn FnMut(Vector2F, &mut EventContext)>>, drag_handler: Option<Box<dyn FnMut(Vector2F, &mut EventContext)>>,
right_mouse_down_handler: Option<Box<dyn FnMut(Vector2F, &mut EventContext)>>,
right_click_handler: Option<Box<dyn FnMut(Vector2F, usize, &mut EventContext)>>,
padding: Padding, padding: Padding,
} }
@ -24,6 +26,7 @@ pub struct MouseEventHandler {
pub struct MouseState { pub struct MouseState {
pub hovered: bool, pub hovered: bool,
pub clicked: bool, pub clicked: bool,
pub right_clicked: bool,
prev_drag_position: Option<Vector2F>, prev_drag_position: Option<Vector2F>,
} }
@ -43,6 +46,8 @@ impl MouseEventHandler {
mouse_down_handler: None, mouse_down_handler: None,
click_handler: None, click_handler: None,
drag_handler: None, drag_handler: None,
right_mouse_down_handler: None,
right_click_handler: None,
padding: Default::default(), padding: Default::default(),
} }
} }
@ -52,12 +57,18 @@ impl MouseEventHandler {
self 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.mouse_down_handler = Some(Box::new(handler));
self 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.click_handler = Some(Box::new(handler));
self self
} }
@ -67,6 +78,22 @@ impl MouseEventHandler {
self 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 { pub fn with_padding(mut self, padding: Padding) -> Self {
self.padding = padding; self.padding = padding;
self self
@ -120,6 +147,8 @@ impl Element for MouseEventHandler {
let mouse_down_handler = self.mouse_down_handler.as_mut(); let mouse_down_handler = self.mouse_down_handler.as_mut();
let click_handler = self.click_handler.as_mut(); let click_handler = self.click_handler.as_mut();
let drag_handler = self.drag_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); let handled_in_child = self.child.dispatch_event(event, cx);
@ -144,7 +173,7 @@ impl Element for MouseEventHandler {
state.prev_drag_position = Some(*position); state.prev_drag_position = Some(*position);
cx.notify(); cx.notify();
if let Some(handler) = mouse_down_handler { if let Some(handler) = mouse_down_handler {
handler(cx); handler(*position, cx);
} }
true true
} else { } else {
@ -162,7 +191,7 @@ impl Element for MouseEventHandler {
cx.notify(); cx.notify();
if let Some(handler) = click_handler { if let Some(handler) = click_handler {
if hit_bounds.contains_point(*position) { if hit_bounds.contains_point(*position) {
handler(*click_count, cx); handler(*position, *click_count, cx);
} }
} }
true true
@ -184,6 +213,36 @@ impl Element for MouseEventHandler {
handled_in_child 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, _ => handled_in_child,
}) })
} }

View File

@ -1,16 +1,28 @@
use serde_json::json;
use crate::{ use crate::{
geometry::{rect::RectF, vector::Vector2F}, geometry::{rect::RectF, vector::Vector2F},
json::ToJson,
DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
SizeConstraint, SizeConstraint,
}; };
pub struct Overlay { pub struct Overlay {
child: ElementBox, child: ElementBox,
abs_position: Option<Vector2F>,
} }
impl Overlay { impl Overlay {
pub fn new(child: ElementBox) -> Self { 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, constraint: SizeConstraint,
cx: &mut LayoutContext, cx: &mut LayoutContext,
) -> (Vector2F, Self::LayoutState) { ) -> (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); let size = self.child.layout(constraint, cx);
(Vector2F::zero(), size) (Vector2F::zero(), size)
} }
@ -34,9 +51,10 @@ impl Element for Overlay {
size: &mut Self::LayoutState, size: &mut Self::LayoutState,
cx: &mut PaintContext, 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); 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(); cx.scene.pop_stacking_context();
} }
@ -59,6 +77,10 @@ impl Element for Overlay {
_: &Self::PaintState, _: &Self::PaintState,
cx: &DebugContext, cx: &DebugContext,
) -> serde_json::Value { ) -> serde_json::Value {
self.child.debug(cx) json!({
"type": "Overlay",
"abs_position": self.abs_position.to_json(),
"child": self.child.debug(cx),
})
} }
} }

View File

@ -43,6 +43,7 @@ pub enum Event {
}, },
RightMouseUp { RightMouseUp {
position: Vector2F, position: Vector2F,
click_count: usize,
}, },
NavigateMouseDown { NavigateMouseDown {
position: Vector2F, position: Vector2F,
@ -72,7 +73,7 @@ impl Event {
| Event::LeftMouseUp { position, .. } | Event::LeftMouseUp { position, .. }
| Event::LeftMouseDragged { position } | Event::LeftMouseDragged { position }
| Event::RightMouseDown { position, .. } | Event::RightMouseDown { position, .. }
| Event::RightMouseUp { position } | Event::RightMouseUp { position, .. }
| Event::NavigateMouseDown { position, .. } | Event::NavigateMouseDown { position, .. }
| Event::NavigateMouseUp { position, .. } | Event::NavigateMouseUp { position, .. }
| Event::MouseMoved { position, .. } => Some(*position), | Event::MouseMoved { position, .. } => Some(*position),

View File

@ -178,6 +178,7 @@ impl Event {
native_event.locationInWindow().x as f32, native_event.locationInWindow().x as f32,
window_height - native_event.locationInWindow().y as f32, window_height - native_event.locationInWindow().y as f32,
), ),
click_count: native_event.clickCount() as usize,
}), }),
NSEventType::NSOtherMouseDown => { NSEventType::NSOtherMouseDown => {
let direction = match native_event.buttonNumber() { let direction = match native_event.buttonNumber() {

View File

@ -134,15 +134,16 @@ impl Presenter {
scene 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) { if let Some(root_view_id) = cx.root_view_id(self.window_id) {
self.build_layout_context(refreshing, cx) self.build_layout_context(window_size, refreshing, cx)
.layout(root_view_id, SizeConstraint::strict(size)); .layout(root_view_id, SizeConstraint::strict(window_size));
} }
} }
pub fn build_layout_context<'a>( pub fn build_layout_context<'a>(
&'a mut self, &'a mut self,
window_size: Vector2F,
refreshing: bool, refreshing: bool,
cx: &'a mut MutableAppContext, cx: &'a mut MutableAppContext,
) -> LayoutContext<'a> { ) -> LayoutContext<'a> {
@ -150,6 +151,7 @@ impl Presenter {
rendered_views: &mut self.rendered_views, rendered_views: &mut self.rendered_views,
parents: &mut self.parents, parents: &mut self.parents,
refreshing, refreshing,
window_size,
font_cache: &self.font_cache, font_cache: &self.font_cache,
font_system: cx.platform().fonts(), font_system: cx.platform().fonts(),
text_layout_cache: &self.text_layout_cache, text_layout_cache: &self.text_layout_cache,
@ -259,6 +261,7 @@ pub struct LayoutContext<'a> {
parents: &'a mut HashMap<usize, usize>, parents: &'a mut HashMap<usize, usize>,
view_stack: Vec<usize>, view_stack: Vec<usize>,
pub refreshing: bool, pub refreshing: bool,
pub window_size: Vector2F,
pub font_cache: &'a Arc<FontCache>, pub font_cache: &'a Arc<FontCache>,
pub font_system: Arc<dyn FontSystem>, pub font_system: Arc<dyn FontSystem>,
pub text_layout_cache: &'a TextLayoutCache, pub text_layout_cache: &'a TextLayoutCache,

View File

@ -119,7 +119,7 @@ impl View for Select {
.with_style(style.header) .with_style(style.header)
.boxed() .boxed()
}) })
.on_click(move |_, cx| cx.dispatch_action(ToggleSelect)) .on_click(move |_, _, cx| cx.dispatch_action(ToggleSelect))
.boxed(), .boxed(),
); );
if self.is_open { 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() .boxed()
})) }))
}, },

View File

@ -90,7 +90,7 @@ impl<D: PickerDelegate> View for Picker<D> {
.read(cx) .read(cx)
.render_match(ix, state, ix == selected_ix, 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) .with_cursor_style(CursorStyle::PointingHand)
.boxed() .boxed()
})); }));

View File

@ -4,13 +4,14 @@ use gpui::{
actions, actions,
anyhow::{anyhow, Result}, anyhow::{anyhow, Result},
elements::{ elements::{
ChildView, ConstrainedBox, Empty, Flex, Label, MouseEventHandler, ParentElement, ChildView, ConstrainedBox, Empty, Flex, Label, MouseEventHandler, Overlay, ParentElement,
ScrollTarget, Svg, UniformList, UniformListState, ScrollTarget, Stack, Svg, UniformList, UniformListState,
}, },
geometry::vector::Vector2F,
impl_internal_actions, keymap, impl_internal_actions, keymap,
platform::CursorStyle, platform::CursorStyle,
AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, PromptLevel, Task, AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, PromptLevel,
View, ViewContext, ViewHandle, WeakViewHandle, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle,
}; };
use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
use settings::Settings; use settings::Settings;
@ -36,6 +37,7 @@ pub struct ProjectPanel {
selection: Option<Selection>, selection: Option<Selection>,
edit_state: Option<EditState>, edit_state: Option<EditState>,
filename_editor: ViewHandle<Editor>, filename_editor: ViewHandle<Editor>,
context_menu: Option<ContextMenu>,
handle: WeakViewHandle<Self>, handle: WeakViewHandle<Self>,
} }
@ -75,6 +77,17 @@ pub struct Open {
pub change_focus: bool, pub change_focus: bool,
} }
#[derive(Clone)]
pub struct DeployContextMenu {
pub position: Vector2F,
pub entry_id: Option<ProjectEntryId>,
}
pub struct ContextMenu {
pub position: Vector2F,
pub entry_id: Option<ProjectEntryId>,
}
actions!( actions!(
project_panel, project_panel,
[ [
@ -86,9 +99,10 @@ actions!(
Rename Rename
] ]
); );
impl_internal_actions!(project_panel, [Open, ToggleExpanded]); impl_internal_actions!(project_panel, [Open, ToggleExpanded, DeployContextMenu]);
pub fn init(cx: &mut MutableAppContext) { pub fn init(cx: &mut MutableAppContext) {
cx.add_action(ProjectPanel::deploy_context_menu);
cx.add_action(ProjectPanel::expand_selected_entry); cx.add_action(ProjectPanel::expand_selected_entry);
cx.add_action(ProjectPanel::collapse_selected_entry); cx.add_action(ProjectPanel::collapse_selected_entry);
cx.add_action(ProjectPanel::toggle_expanded); cx.add_action(ProjectPanel::toggle_expanded);
@ -156,6 +170,7 @@ impl ProjectPanel {
selection: None, selection: None,
edit_state: None, edit_state: None,
filename_editor, filename_editor,
context_menu: None,
handle: cx.weak_handle(), handle: cx.weak_handle(),
}; };
this.update_visible_entries(None, cx); this.update_visible_entries(None, cx);
@ -195,6 +210,14 @@ impl ProjectPanel {
project_panel project_panel
} }
fn deploy_context_menu(&mut self, action: &DeployContextMenu, cx: &mut ViewContext<Self>) {
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<Self>) { fn expand_selected_entry(&mut self, _: &ExpandSelectedEntry, cx: &mut ViewContext<Self>) {
if let Some((worktree, entry)) = self.selected_entry(cx) { if let Some((worktree, entry)) = self.selected_entry(cx) {
let expanded_dir_ids = let expanded_dir_ids =
@ -841,7 +864,7 @@ impl ProjectPanel {
.with_padding_left(padding) .with_padding_left(padding)
.boxed() .boxed()
}) })
.on_click(move |click_count, cx| { .on_click(move |_, click_count, cx| {
if kind == EntryKind::Dir { if kind == EntryKind::Dir {
cx.dispatch_action(ToggleExpanded(entry_id)) cx.dispatch_action(ToggleExpanded(entry_id))
} else { } 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) .with_cursor_style(CursorStyle::PointingHand)
.boxed() .boxed()
} }
fn render_context_menu(&self, cx: &mut RenderContext<Self>) -> Option<ElementBox> {
self.context_menu.as_ref().map(|menu| {
let style = &cx.global::<Settings>().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 { impl View for ProjectPanel {
@ -866,33 +913,38 @@ impl View for ProjectPanel {
let mut container_style = theme.container; let mut container_style = theme.container;
let padding = std::mem::take(&mut container_style.padding); let padding = std::mem::take(&mut container_style.padding);
let handle = self.handle.clone(); let handle = self.handle.clone();
UniformList::new( Stack::new()
self.list.clone(), .with_child(
self.visible_entries UniformList::new(
.iter() self.list.clone(),
.map(|(_, worktree_entries)| worktree_entries.len()) self.visible_entries
.sum(), .iter()
move |range, items, cx| { .map(|(_, worktree_entries)| worktree_entries.len())
let theme = cx.global::<Settings>().theme.clone(); .sum(),
let this = handle.upgrade(cx).unwrap(); move |range, items, cx| {
this.update(cx.app, |this, cx| { let theme = cx.global::<Settings>().theme.clone();
this.for_each_visible_entry(range.clone(), cx, |id, details, cx| { let this = handle.upgrade(cx).unwrap();
items.push(Self::render_entry( this.update(cx.app, |this, cx| {
id, this.for_each_visible_entry(range.clone(), cx, |id, details, cx| {
details, items.push(Self::render_entry(
&this.filename_editor, id,
&theme.project_panel, details,
cx, &this.filename_editor,
)); &theme.project_panel,
}); cx,
}) ));
}, });
) })
.with_padding_top(padding.top) },
.with_padding_bottom(padding.bottom) )
.contained() .with_padding_top(padding.top)
.with_style(container_style) .with_padding_bottom(padding.bottom)
.boxed() .contained()
.with_style(container_style)
.boxed(),
)
.with_children(self.render_context_menu(cx))
.boxed()
} }
fn keymap_context(&self, _: &AppContext) -> keymap::Context { fn keymap_context(&self, _: &AppContext) -> keymap::Context {

View File

@ -290,7 +290,7 @@ impl BufferSearchBar {
.with_style(style.container) .with_style(style.container)
.boxed() .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) .with_cursor_style(CursorStyle::PointingHand)
.boxed() .boxed()
} }
@ -314,7 +314,7 @@ impl BufferSearchBar {
.with_style(style.container) .with_style(style.container)
.boxed() .boxed()
}) })
.on_click(move |_, cx| match direction { .on_click(move |_, _, cx| match direction {
Direction::Prev => cx.dispatch_action(SelectPrevMatch), Direction::Prev => cx.dispatch_action(SelectPrevMatch),
Direction::Next => cx.dispatch_action(SelectNextMatch), Direction::Next => cx.dispatch_action(SelectNextMatch),
}) })

View File

@ -672,7 +672,7 @@ impl ProjectSearchBar {
.with_style(style.container) .with_style(style.container)
.boxed() .boxed()
}) })
.on_click(move |_, cx| match direction { .on_click(move |_, _, cx| match direction {
Direction::Prev => cx.dispatch_action(SelectPrevMatch), Direction::Prev => cx.dispatch_action(SelectPrevMatch),
Direction::Next => cx.dispatch_action(SelectNextMatch), Direction::Next => cx.dispatch_action(SelectNextMatch),
}) })
@ -699,7 +699,7 @@ impl ProjectSearchBar {
.with_style(style.container) .with_style(style.container)
.boxed() .boxed()
}) })
.on_click(move |_, cx| cx.dispatch_action(ToggleSearchOption(option))) .on_click(move |_, _, cx| cx.dispatch_action(ToggleSearchOption(option)))
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.boxed() .boxed()
} }

View File

@ -226,6 +226,7 @@ pub struct ProjectPanel {
pub ignored_entry_fade: f32, pub ignored_entry_fade: f32,
pub filename_editor: FieldEditor, pub filename_editor: FieldEditor,
pub indent_width: f32, pub indent_width: f32,
pub context_menu: ContextMenu,
} }
#[derive(Clone, Debug, Deserialize, Default)] #[derive(Clone, Debug, Deserialize, Default)]
@ -239,6 +240,14 @@ pub struct ProjectPanelEntry {
pub icon_spacing: f32, 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)] #[derive(Debug, Deserialize, Default)]
pub struct CommandPalette { pub struct CommandPalette {
pub key: Interactive<ContainedLabel>, pub key: Interactive<ContainedLabel>,

View File

@ -168,7 +168,8 @@ impl View for LspStatus {
self.failed.join(", "), self.failed.join(", "),
if self.failed.len() > 1 { "s" } else { "" } 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 { } else {
return Empty::new().boxed(); return Empty::new().boxed();
} }

View File

@ -788,7 +788,7 @@ impl Pane {
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click({ .on_click({
let pane = pane.clone(); let pane = pane.clone();
move |_, cx| { move |_, _, cx| {
cx.dispatch_action(CloseItem { cx.dispatch_action(CloseItem {
item_id, item_id,
pane: pane.clone(), pane: pane.clone(),

View File

@ -293,7 +293,7 @@ impl View for SidebarButtons {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(move |_, cx| { .on_click(move |_, _, cx| {
cx.dispatch_action(ToggleSidebarItem { cx.dispatch_action(ToggleSidebarItem {
side, side,
item_index: ix, item_index: ix,

View File

@ -1730,7 +1730,7 @@ impl Workspace {
.with_style(style.container) .with_style(style.container)
.boxed() .boxed()
}) })
.on_click(|_, cx| cx.dispatch_action(Authenticate)) .on_click(|_, _, cx| cx.dispatch_action(Authenticate))
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.aligned() .aligned()
.boxed(), .boxed(),
@ -1781,7 +1781,7 @@ impl Workspace {
if let Some(peer_id) = peer_id { if let Some(peer_id) = peer_id {
MouseEventHandler::new::<ToggleFollow, _, _>(replica_id.into(), cx, move |_, _| content) MouseEventHandler::new::<ToggleFollow, _, _>(replica_id.into(), cx, move |_, _| content)
.with_cursor_style(CursorStyle::PointingHand) .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() .boxed()
} else { } else {
content content

View File

@ -1,6 +1,6 @@
import Theme from "../themes/common/theme"; import Theme from "../themes/common/theme";
import { panel } from "./app"; 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) { export default function projectPanel(theme: Theme) {
return { return {
@ -32,5 +32,19 @@ export default function projectPanel(theme: Theme) {
text: text(theme, "mono", "primary", { size: "sm" }), text: text(theme, "mono", "primary", { size: "sm" }),
selection: player(theme, 1).selection, 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),
}
}; };
} }