project panel: Always show paste in context menu (and grey it out when it's disabled) (#17262)

![image](https://github.com/user-attachments/assets/df471567-bdb9-494b-96a5-84d1da47583f)

Release Notes:

- "Paste" is now always shown in project panel context menu.
This commit is contained in:
Piotr Osiewicz 2024-09-02 13:44:21 +02:00 committed by GitHub
parent b578be5c77
commit b6cf576d66
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 53 additions and 8 deletions

View file

@ -499,8 +499,12 @@ impl ProjectPanel {
.action("Copy", Box::new(Copy)) .action("Copy", Box::new(Copy))
.action("Duplicate", Box::new(Duplicate)) .action("Duplicate", Box::new(Duplicate))
// TODO: Paste should always be visible, cbut disabled when clipboard is empty // TODO: Paste should always be visible, cbut disabled when clipboard is empty
.when(self.clipboard.as_ref().is_some(), |menu| { .map(|menu| {
if self.clipboard.as_ref().is_some() {
menu.action("Paste", Box::new(Paste)) menu.action("Paste", Box::new(Paste))
} else {
menu.disabled_action("Paste", Box::new(Paste))
}
}) })
.separator() .separator()
.action("Copy Path", Box::new(CopyPath)) .action("Copy Path", Box::new(CopyPath))

View file

@ -21,6 +21,7 @@ enum ContextMenuItem {
icon: Option<IconName>, icon: Option<IconName>,
handler: Rc<dyn Fn(Option<&FocusHandle>, &mut WindowContext)>, handler: Rc<dyn Fn(Option<&FocusHandle>, &mut WindowContext)>,
action: Option<Box<dyn Action>>, action: Option<Box<dyn Action>>,
disabled: bool,
}, },
CustomEntry { CustomEntry {
entry_render: Box<dyn Fn(&mut WindowContext) -> AnyElement>, entry_render: Box<dyn Fn(&mut WindowContext) -> AnyElement>,
@ -102,6 +103,7 @@ impl ContextMenu {
handler: Rc::new(move |_, cx| handler(cx)), handler: Rc::new(move |_, cx| handler(cx)),
icon: None, icon: None,
action, action,
disabled: false,
}); });
self self
} }
@ -120,6 +122,7 @@ impl ContextMenu {
handler: Rc::new(move |_, cx| handler(cx)), handler: Rc::new(move |_, cx| handler(cx)),
icon: None, icon: None,
action, action,
disabled: false,
}); });
self self
} }
@ -167,6 +170,29 @@ impl ContextMenu {
cx.dispatch_action(action.boxed_clone()); cx.dispatch_action(action.boxed_clone());
}), }),
icon: None, icon: None,
disabled: false,
});
self
}
pub fn disabled_action(
mut self,
label: impl Into<SharedString>,
action: Box<dyn Action>,
) -> Self {
self.items.push(ContextMenuItem::Entry {
toggle: None,
label: label.into(),
action: Some(action.boxed_clone()),
handler: Rc::new(move |context, cx| {
if let Some(context) = &context {
cx.focus(context);
}
cx.dispatch_action(action.boxed_clone());
}),
icon: None,
disabled: true,
}); });
self self
} }
@ -179,6 +205,7 @@ impl ContextMenu {
action: Some(action.boxed_clone()), action: Some(action.boxed_clone()),
handler: Rc::new(move |_, cx| cx.dispatch_action(action.boxed_clone())), handler: Rc::new(move |_, cx| cx.dispatch_action(action.boxed_clone())),
icon: Some(IconName::ArrowUpRight), icon: Some(IconName::ArrowUpRight),
disabled: false,
}); });
self self
} }
@ -187,7 +214,11 @@ impl ContextMenu {
let context = self.action_context.as_ref(); let context = self.action_context.as_ref();
match self.selected_index.and_then(|ix| self.items.get(ix)) { match self.selected_index.and_then(|ix| self.items.get(ix)) {
Some( Some(
ContextMenuItem::Entry { handler, .. } ContextMenuItem::Entry {
handler,
disabled: false,
..
}
| ContextMenuItem::CustomEntry { handler, .. }, | ContextMenuItem::CustomEntry { handler, .. },
) => (handler)(context, cx), ) => (handler)(context, cx),
_ => {} _ => {}
@ -259,6 +290,7 @@ impl ContextMenu {
if let Some(ix) = self.items.iter().position(|item| { if let Some(ix) = self.items.iter().position(|item| {
if let ContextMenuItem::Entry { if let ContextMenuItem::Entry {
action: Some(action), action: Some(action),
disabled: false,
.. ..
} = item } = item
{ {
@ -298,7 +330,7 @@ impl ContextMenuItem {
ContextMenuItem::Header(_) ContextMenuItem::Header(_)
| ContextMenuItem::Separator | ContextMenuItem::Separator
| ContextMenuItem::Label { .. } => false, | ContextMenuItem::Label { .. } => false,
ContextMenuItem::Entry { .. } => true, ContextMenuItem::Entry { disabled, .. } => !disabled,
ContextMenuItem::CustomEntry { selectable, .. } => *selectable, ContextMenuItem::CustomEntry { selectable, .. } => *selectable,
} }
} }
@ -328,6 +360,7 @@ impl Render for ContextMenu {
for item in self.items.iter() { for item in self.items.iter() {
if let ContextMenuItem::Entry { if let ContextMenuItem::Entry {
action: Some(action), action: Some(action),
disabled: false,
.. ..
} = item } = item
{ {
@ -360,22 +393,30 @@ impl Render for ContextMenu {
handler, handler,
icon, icon,
action, action,
disabled,
} => { } => {
let handler = handler.clone(); let handler = handler.clone();
let menu = cx.view().downgrade(); let menu = cx.view().downgrade();
let color = if *disabled {
Color::Muted
} else {
Color::Default
};
let label_element = if let Some(icon) = icon { let label_element = if let Some(icon) = icon {
h_flex() h_flex()
.gap_1() .gap_1()
.child(Label::new(label.clone())) .child(Label::new(label.clone()).color(color))
.child(Icon::new(*icon).size(IconSize::Small)) .child(
Icon::new(*icon).size(IconSize::Small).color(color),
)
.into_any_element() .into_any_element()
} else { } else {
Label::new(label.clone()).into_any_element() Label::new(label.clone()).color(color).into_any_element()
}; };
ListItem::new(ix) ListItem::new(ix)
.inset(true) .inset(true)
.disabled(*disabled)
.selected(Some(ix) == self.selected_index) .selected(Some(ix) == self.selected_index)
.when_some(*toggle, |list_item, (position, toggled)| { .when_some(*toggle, |list_item, (position, toggled)| {
let contents = if toggled { let contents = if toggled {