Allow passing a handler function to context menu items
This commit is contained in:
parent
d3b976d044
commit
5521ff1b22
8 changed files with 137 additions and 78 deletions
|
@ -301,13 +301,19 @@ impl CollabTitlebarItem {
|
|||
.with_style(item_style.container)
|
||||
.into_any()
|
||||
})),
|
||||
ContextMenuItem::item("Sign out", SignOut),
|
||||
ContextMenuItem::item("Send Feedback", feedback::feedback_editor::GiveFeedback),
|
||||
ContextMenuItem::action("Sign out", SignOut),
|
||||
ContextMenuItem::action(
|
||||
"Send Feedback",
|
||||
feedback::feedback_editor::GiveFeedback,
|
||||
),
|
||||
]
|
||||
} else {
|
||||
vec![
|
||||
ContextMenuItem::item("Sign in", SignIn),
|
||||
ContextMenuItem::item("Send Feedback", feedback::feedback_editor::GiveFeedback),
|
||||
ContextMenuItem::action("Sign in", SignIn),
|
||||
ContextMenuItem::action(
|
||||
"Send Feedback",
|
||||
feedback::feedback_editor::GiveFeedback,
|
||||
),
|
||||
]
|
||||
};
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ use gpui::{
|
|||
};
|
||||
use menu::*;
|
||||
use settings::Settings;
|
||||
use std::{any::TypeId, borrow::Cow, time::Duration};
|
||||
use std::{any::TypeId, borrow::Cow, sync::Arc, time::Duration};
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
struct Clicked;
|
||||
|
@ -64,20 +64,44 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
pub enum ContextMenuItemAction {
|
||||
Action(Box<dyn Action>),
|
||||
Handler(Arc<dyn Fn(&mut ViewContext<ContextMenu>)>),
|
||||
}
|
||||
|
||||
impl Clone for ContextMenuItemAction {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
Self::Action(action) => Self::Action(action.boxed_clone()),
|
||||
Self::Handler(handler) => Self::Handler(handler.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ContextMenuItem {
|
||||
Item {
|
||||
label: ContextMenuItemLabel,
|
||||
action: Box<dyn Action>,
|
||||
action: ContextMenuItemAction,
|
||||
},
|
||||
Static(StaticItem),
|
||||
Separator,
|
||||
}
|
||||
|
||||
impl ContextMenuItem {
|
||||
pub fn item(label: impl Into<ContextMenuItemLabel>, action: impl 'static + Action) -> Self {
|
||||
pub fn action(label: impl Into<ContextMenuItemLabel>, action: impl 'static + Action) -> Self {
|
||||
Self::Item {
|
||||
label: label.into(),
|
||||
action: Box::new(action),
|
||||
action: ContextMenuItemAction::Action(Box::new(action)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handler(
|
||||
label: impl Into<ContextMenuItemLabel>,
|
||||
handler: impl 'static + Fn(&mut ViewContext<ContextMenu>),
|
||||
) -> Self {
|
||||
Self::Item {
|
||||
label: label.into(),
|
||||
action: ContextMenuItemAction::Handler(Arc::new(handler)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,7 +115,10 @@ impl ContextMenuItem {
|
|||
|
||||
fn action_id(&self) -> Option<TypeId> {
|
||||
match self {
|
||||
ContextMenuItem::Item { action, .. } => Some(action.id()),
|
||||
ContextMenuItem::Item { action, .. } => match action {
|
||||
ContextMenuItemAction::Action(action) => Some(action.id()),
|
||||
ContextMenuItemAction::Handler(_) => None,
|
||||
},
|
||||
ContextMenuItem::Static(..) | ContextMenuItem::Separator => None,
|
||||
}
|
||||
}
|
||||
|
@ -208,7 +235,17 @@ impl ContextMenu {
|
|||
fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
|
||||
if let Some(ix) = self.selected_index {
|
||||
if let Some(ContextMenuItem::Item { action, .. }) = self.items.get(ix) {
|
||||
cx.dispatch_any_action(action.boxed_clone());
|
||||
match action {
|
||||
ContextMenuItemAction::Action(action) => {
|
||||
let window_id = cx.window_id();
|
||||
cx.dispatch_any_action_at(
|
||||
window_id,
|
||||
self.parent_view_id,
|
||||
action.boxed_clone(),
|
||||
);
|
||||
}
|
||||
ContextMenuItemAction::Handler(handler) => handler(cx),
|
||||
}
|
||||
self.reset(cx);
|
||||
}
|
||||
}
|
||||
|
@ -351,13 +388,16 @@ impl ContextMenu {
|
|||
Some(ix) == self.selected_index,
|
||||
);
|
||||
|
||||
KeystrokeLabel::new(
|
||||
self.parent_view_id,
|
||||
action.boxed_clone(),
|
||||
style.keystroke.container,
|
||||
style.keystroke.text.clone(),
|
||||
)
|
||||
.into_any()
|
||||
match action {
|
||||
ContextMenuItemAction::Action(action) => KeystrokeLabel::new(
|
||||
self.parent_view_id,
|
||||
action.boxed_clone(),
|
||||
style.keystroke.container,
|
||||
style.keystroke.text.clone(),
|
||||
)
|
||||
.into_any(),
|
||||
ContextMenuItemAction::Handler(_) => Empty::new().into_any(),
|
||||
}
|
||||
}
|
||||
|
||||
ContextMenuItem::Static(_) => Empty::new().into_any(),
|
||||
|
@ -389,11 +429,23 @@ impl ContextMenu {
|
|||
.with_children(self.items.iter().enumerate().map(|(ix, item)| {
|
||||
match item {
|
||||
ContextMenuItem::Item { label, action } => {
|
||||
let action = action.boxed_clone();
|
||||
let action = action.clone();
|
||||
let view_id = self.parent_view_id;
|
||||
MouseEventHandler::<MenuItem, ContextMenu>::new(ix, cx, |state, _| {
|
||||
let style =
|
||||
style.item.style_for(state, Some(ix) == self.selected_index);
|
||||
let keystroke = match &action {
|
||||
ContextMenuItemAction::Action(action) => Some(
|
||||
KeystrokeLabel::new(
|
||||
view_id,
|
||||
action.boxed_clone(),
|
||||
style.keystroke.container,
|
||||
style.keystroke.text.clone(),
|
||||
)
|
||||
.flex_float(),
|
||||
),
|
||||
ContextMenuItemAction::Handler(_) => None,
|
||||
};
|
||||
|
||||
Flex::row()
|
||||
.with_child(match label {
|
||||
|
@ -406,15 +458,7 @@ impl ContextMenu {
|
|||
element(state, style)
|
||||
}
|
||||
})
|
||||
.with_child({
|
||||
KeystrokeLabel::new(
|
||||
view_id,
|
||||
action.boxed_clone(),
|
||||
style.keystroke.container,
|
||||
style.keystroke.text.clone(),
|
||||
)
|
||||
.flex_float()
|
||||
})
|
||||
.with_children(keystroke)
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
})
|
||||
|
@ -424,7 +468,16 @@ impl ContextMenu {
|
|||
.on_click(MouseButton::Left, move |_, _, cx| {
|
||||
let window_id = cx.window_id();
|
||||
cx.dispatch_action(Clicked);
|
||||
cx.dispatch_any_action_at(window_id, view_id, action.boxed_clone());
|
||||
match &action {
|
||||
ContextMenuItemAction::Action(action) => {
|
||||
cx.dispatch_any_action_at(
|
||||
window_id,
|
||||
view_id,
|
||||
action.boxed_clone(),
|
||||
);
|
||||
}
|
||||
ContextMenuItemAction::Handler(handler) => handler(cx),
|
||||
}
|
||||
})
|
||||
.on_drag(MouseButton::Left, |_, _, _| {})
|
||||
.into_any()
|
||||
|
|
|
@ -271,8 +271,8 @@ impl CopilotButton {
|
|||
) {
|
||||
let mut menu_options = Vec::with_capacity(2);
|
||||
|
||||
menu_options.push(ContextMenuItem::item("Sign In", InitiateSignIn));
|
||||
menu_options.push(ContextMenuItem::item("Disable Copilot", HideCopilot));
|
||||
menu_options.push(ContextMenuItem::action("Sign In", InitiateSignIn));
|
||||
menu_options.push(ContextMenuItem::action("Disable Copilot", HideCopilot));
|
||||
|
||||
self.popup_menu.update(cx, |menu, cx| {
|
||||
menu.show(
|
||||
|
@ -292,7 +292,7 @@ impl CopilotButton {
|
|||
if let Some(language) = &self.language {
|
||||
let language_enabled = settings.show_copilot_suggestions(Some(language.as_ref()));
|
||||
|
||||
menu_options.push(ContextMenuItem::item(
|
||||
menu_options.push(ContextMenuItem::action(
|
||||
format!(
|
||||
"{} Suggestions for {}",
|
||||
if language_enabled { "Hide" } else { "Show" },
|
||||
|
@ -305,7 +305,7 @@ impl CopilotButton {
|
|||
}
|
||||
|
||||
let globally_enabled = cx.global::<Settings>().show_copilot_suggestions(None);
|
||||
menu_options.push(ContextMenuItem::item(
|
||||
menu_options.push(ContextMenuItem::action(
|
||||
if globally_enabled {
|
||||
"Hide Suggestions for All Files"
|
||||
} else {
|
||||
|
@ -317,7 +317,7 @@ impl CopilotButton {
|
|||
menu_options.push(ContextMenuItem::Separator);
|
||||
|
||||
let icon_style = settings.theme.copilot.out_link_icon.clone();
|
||||
menu_options.push(ContextMenuItem::item(
|
||||
menu_options.push(ContextMenuItem::action(
|
||||
move |state: &mut MouseState, style: &theme::ContextMenuItem| {
|
||||
Flex::row()
|
||||
.with_child(Label::new("Copilot Settings", style.label.clone()))
|
||||
|
@ -328,7 +328,7 @@ impl CopilotButton {
|
|||
OsOpen::new(COPILOT_SETTINGS_URL),
|
||||
));
|
||||
|
||||
menu_options.push(ContextMenuItem::item("Sign Out", SignOut));
|
||||
menu_options.push(ContextMenuItem::action("Sign Out", SignOut));
|
||||
|
||||
self.popup_menu.update(cx, |menu, cx| {
|
||||
menu.show(
|
||||
|
|
|
@ -51,18 +51,18 @@ pub fn deploy_context_menu(
|
|||
position,
|
||||
AnchorCorner::TopLeft,
|
||||
vec![
|
||||
ContextMenuItem::item("Rename Symbol", Rename),
|
||||
ContextMenuItem::item("Go to Definition", GoToDefinition),
|
||||
ContextMenuItem::item("Go to Type Definition", GoToTypeDefinition),
|
||||
ContextMenuItem::item("Find All References", FindAllReferences),
|
||||
ContextMenuItem::item(
|
||||
ContextMenuItem::action("Rename Symbol", Rename),
|
||||
ContextMenuItem::action("Go to Definition", GoToDefinition),
|
||||
ContextMenuItem::action("Go to Type Definition", GoToTypeDefinition),
|
||||
ContextMenuItem::action("Find All References", FindAllReferences),
|
||||
ContextMenuItem::action(
|
||||
"Code Actions",
|
||||
ToggleCodeActions {
|
||||
deployed_from_indicator: false,
|
||||
},
|
||||
),
|
||||
ContextMenuItem::Separator,
|
||||
ContextMenuItem::item("Reveal in Finder", RevealInFinder),
|
||||
ContextMenuItem::action("Reveal in Finder", RevealInFinder),
|
||||
],
|
||||
cx,
|
||||
);
|
||||
|
|
|
@ -296,38 +296,38 @@ impl ProjectPanel {
|
|||
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::item(
|
||||
menu_entries.push(ContextMenuItem::action(
|
||||
"Add Folder to Project",
|
||||
workspace::AddFolderToProject,
|
||||
));
|
||||
if is_root {
|
||||
menu_entries.push(ContextMenuItem::item(
|
||||
menu_entries.push(ContextMenuItem::action(
|
||||
"Remove from Project",
|
||||
workspace::RemoveWorktreeFromProject(worktree_id),
|
||||
));
|
||||
}
|
||||
}
|
||||
menu_entries.push(ContextMenuItem::item("New File", NewFile));
|
||||
menu_entries.push(ContextMenuItem::item("New Folder", NewDirectory));
|
||||
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::item("Cut", Cut));
|
||||
menu_entries.push(ContextMenuItem::item("Copy", Copy));
|
||||
menu_entries.push(ContextMenuItem::action("Cut", Cut));
|
||||
menu_entries.push(ContextMenuItem::action("Copy", Copy));
|
||||
menu_entries.push(ContextMenuItem::Separator);
|
||||
menu_entries.push(ContextMenuItem::item("Copy Path", CopyPath));
|
||||
menu_entries.push(ContextMenuItem::item(
|
||||
menu_entries.push(ContextMenuItem::action("Copy Path", CopyPath));
|
||||
menu_entries.push(ContextMenuItem::action(
|
||||
"Copy Relative Path",
|
||||
CopyRelativePath,
|
||||
));
|
||||
menu_entries.push(ContextMenuItem::item("Reveal in Finder", RevealInFinder));
|
||||
menu_entries.push(ContextMenuItem::action("Reveal in Finder", RevealInFinder));
|
||||
if let Some(clipboard_entry) = self.clipboard_entry {
|
||||
if clipboard_entry.worktree_id() == worktree.id() {
|
||||
menu_entries.push(ContextMenuItem::item("Paste", Paste));
|
||||
menu_entries.push(ContextMenuItem::action("Paste", Paste));
|
||||
}
|
||||
}
|
||||
menu_entries.push(ContextMenuItem::Separator);
|
||||
menu_entries.push(ContextMenuItem::item("Rename", Rename));
|
||||
menu_entries.push(ContextMenuItem::action("Rename", Rename));
|
||||
if !is_root {
|
||||
menu_entries.push(ContextMenuItem::item("Delete", Delete));
|
||||
menu_entries.push(ContextMenuItem::action("Delete", Delete));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -134,7 +134,7 @@ impl TerminalButton {
|
|||
_action: &DeployTerminalMenu,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let mut menu_options = vec![ContextMenuItem::item("New Terminal", NewTerminal)];
|
||||
let mut menu_options = vec![ContextMenuItem::action("New Terminal", NewTerminal)];
|
||||
|
||||
if let Some(workspace) = self.workspace.upgrade(cx) {
|
||||
let project = workspace.read(cx).project().read(cx);
|
||||
|
@ -146,7 +146,7 @@ impl TerminalButton {
|
|||
|
||||
for local_terminal_handle in local_terminal_handles {
|
||||
if let Some(terminal) = local_terminal_handle.upgrade(cx) {
|
||||
menu_options.push(ContextMenuItem::item(
|
||||
menu_options.push(ContextMenuItem::action(
|
||||
terminal.read(cx).title(),
|
||||
FocusTerminal {
|
||||
terminal_handle: local_terminal_handle.clone(),
|
||||
|
|
|
@ -199,8 +199,8 @@ impl TerminalView {
|
|||
|
||||
pub fn deploy_context_menu(&mut self, action: &DeployContextMenu, cx: &mut ViewContext<Self>) {
|
||||
let menu_entries = vec![
|
||||
ContextMenuItem::item("Clear", Clear),
|
||||
ContextMenuItem::item("Close", pane::CloseActiveItem),
|
||||
ContextMenuItem::action("Clear", Clear),
|
||||
ContextMenuItem::action("Close", pane::CloseActiveItem),
|
||||
];
|
||||
|
||||
self.context_menu.update(cx, |menu, cx| {
|
||||
|
|
|
@ -1229,10 +1229,10 @@ impl Pane {
|
|||
Default::default(),
|
||||
AnchorCorner::TopRight,
|
||||
vec![
|
||||
ContextMenuItem::item("Split Right", SplitRight),
|
||||
ContextMenuItem::item("Split Left", SplitLeft),
|
||||
ContextMenuItem::item("Split Up", SplitUp),
|
||||
ContextMenuItem::item("Split Down", SplitDown),
|
||||
ContextMenuItem::action("Split Right", SplitRight),
|
||||
ContextMenuItem::action("Split Left", SplitLeft),
|
||||
ContextMenuItem::action("Split Up", SplitUp),
|
||||
ContextMenuItem::action("Split Down", SplitDown),
|
||||
],
|
||||
cx,
|
||||
);
|
||||
|
@ -1247,9 +1247,9 @@ impl Pane {
|
|||
Default::default(),
|
||||
AnchorCorner::TopRight,
|
||||
vec![
|
||||
ContextMenuItem::item("Anchor Dock Right", AnchorDockRight),
|
||||
ContextMenuItem::item("Anchor Dock Bottom", AnchorDockBottom),
|
||||
ContextMenuItem::item("Expand Dock", ExpandDock),
|
||||
ContextMenuItem::action("Anchor Dock Right", AnchorDockRight),
|
||||
ContextMenuItem::action("Anchor Dock Bottom", AnchorDockBottom),
|
||||
ContextMenuItem::action("Expand Dock", ExpandDock),
|
||||
],
|
||||
cx,
|
||||
);
|
||||
|
@ -1264,9 +1264,9 @@ impl Pane {
|
|||
Default::default(),
|
||||
AnchorCorner::TopRight,
|
||||
vec![
|
||||
ContextMenuItem::item("New File", NewFile),
|
||||
ContextMenuItem::item("New Terminal", NewTerminal),
|
||||
ContextMenuItem::item("New Search", NewSearch),
|
||||
ContextMenuItem::action("New File", NewFile),
|
||||
ContextMenuItem::action("New Terminal", NewTerminal),
|
||||
ContextMenuItem::action("New Search", NewSearch),
|
||||
],
|
||||
cx,
|
||||
);
|
||||
|
@ -1293,40 +1293,40 @@ impl Pane {
|
|||
AnchorCorner::TopLeft,
|
||||
if is_active_item {
|
||||
vec![
|
||||
ContextMenuItem::item("Close Active Item", CloseActiveItem),
|
||||
ContextMenuItem::item("Close Inactive Items", CloseInactiveItems),
|
||||
ContextMenuItem::item("Close Clean Items", CloseCleanItems),
|
||||
ContextMenuItem::item("Close Items To The Left", CloseItemsToTheLeft),
|
||||
ContextMenuItem::item("Close Items To The Right", CloseItemsToTheRight),
|
||||
ContextMenuItem::item("Close All Items", CloseAllItems),
|
||||
ContextMenuItem::action("Close Active Item", CloseActiveItem),
|
||||
ContextMenuItem::action("Close Inactive Items", CloseInactiveItems),
|
||||
ContextMenuItem::action("Close Clean Items", CloseCleanItems),
|
||||
ContextMenuItem::action("Close Items To The Left", CloseItemsToTheLeft),
|
||||
ContextMenuItem::action("Close Items To The Right", CloseItemsToTheRight),
|
||||
ContextMenuItem::action("Close All Items", CloseAllItems),
|
||||
]
|
||||
} else {
|
||||
// In the case of the user right clicking on a non-active tab, for some item-closing commands, we need to provide the id of the tab, for the others, we can reuse the existing command.
|
||||
vec![
|
||||
ContextMenuItem::item(
|
||||
ContextMenuItem::action(
|
||||
"Close Inactive Item",
|
||||
CloseItemById {
|
||||
item_id: target_item_id,
|
||||
pane: target_pane.clone(),
|
||||
},
|
||||
),
|
||||
ContextMenuItem::item("Close Inactive Items", CloseInactiveItems),
|
||||
ContextMenuItem::item("Close Clean Items", CloseCleanItems),
|
||||
ContextMenuItem::item(
|
||||
ContextMenuItem::action("Close Inactive Items", CloseInactiveItems),
|
||||
ContextMenuItem::action("Close Clean Items", CloseCleanItems),
|
||||
ContextMenuItem::action(
|
||||
"Close Items To The Left",
|
||||
CloseItemsToTheLeftById {
|
||||
item_id: target_item_id,
|
||||
pane: target_pane.clone(),
|
||||
},
|
||||
),
|
||||
ContextMenuItem::item(
|
||||
ContextMenuItem::action(
|
||||
"Close Items To The Right",
|
||||
CloseItemsToTheRightById {
|
||||
item_id: target_item_id,
|
||||
pane: target_pane.clone(),
|
||||
},
|
||||
),
|
||||
ContextMenuItem::item("Close All Items", CloseAllItems),
|
||||
ContextMenuItem::action("Close All Items", CloseAllItems),
|
||||
]
|
||||
},
|
||||
cx,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue