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("Duplicate", Box::new(Duplicate))
// TODO: Paste should always be visible, cbut disabled when clipboard is empty
.when(self.clipboard.as_ref().is_some(), |menu| {
menu.action("Paste", Box::new(Paste))
.map(|menu| {
if self.clipboard.as_ref().is_some() {
menu.action("Paste", Box::new(Paste))
} else {
menu.disabled_action("Paste", Box::new(Paste))
}
})
.separator()
.action("Copy Path", Box::new(CopyPath))

View file

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