Dispatch actions on focused node

Allows us to implement context menu matching nicely
This commit is contained in:
Conrad Irwin 2023-12-04 22:54:48 +00:00
parent 79773178c8
commit c82fea375d
4 changed files with 85 additions and 31 deletions

View file

@ -7,7 +7,7 @@ use gpui::{
IntoElement, Render, View, VisualContext,
};
use menu::{SelectFirst, SelectLast, SelectNext, SelectPrev};
use std::rc::Rc;
use std::{rc::Rc, time::Duration};
pub enum ContextMenuItem {
Separator,
@ -16,7 +16,7 @@ pub enum ContextMenuItem {
label: SharedString,
icon: Option<Icon>,
handler: Rc<dyn Fn(&mut WindowContext)>,
key_binding: Option<KeyBinding>,
action: Option<Box<dyn Action>>,
},
}
@ -70,8 +70,8 @@ impl ContextMenu {
self.items.push(ContextMenuItem::Entry {
label: label.into(),
handler: Rc::new(on_click),
key_binding: None,
icon: None,
action: None,
});
self
}
@ -84,7 +84,7 @@ impl ContextMenu {
) -> Self {
self.items.push(ContextMenuItem::Entry {
label: label.into(),
key_binding: KeyBinding::for_action(&*action, cx),
action: Some(action.boxed_clone()),
handler: Rc::new(move |cx| cx.dispatch_action(action.boxed_clone())),
icon: None,
});
@ -99,7 +99,7 @@ impl ContextMenu {
) -> Self {
self.items.push(ContextMenuItem::Entry {
label: label.into(),
key_binding: KeyBinding::for_action(&*action, cx),
action: Some(action.boxed_clone()),
handler: Rc::new(move |cx| cx.dispatch_action(action.boxed_clone())),
icon: Some(Icon::Link),
});
@ -161,6 +161,36 @@ impl ContextMenu {
self.select_last(&Default::default(), cx);
}
}
pub fn on_action_dispatch(&mut self, dispatched: &Box<dyn Action>, cx: &mut ViewContext<Self>) {
if let Some(ix) = self.items.iter().position(|item| {
if let ContextMenuItem::Entry {
action: Some(action),
..
} = item
{
action.partial_eq(&**dispatched)
} else {
false
}
}) {
self.selected_index = Some(ix);
cx.notify();
let action = dispatched.boxed_clone();
cx.spawn(|this, mut cx| async move {
cx.background_executor()
.timer(Duration::from_millis(50))
.await;
this.update(&mut cx, |this, cx| {
cx.dispatch_action(action);
this.cancel(&Default::default(), cx)
})
})
.detach_and_log_err(cx);
} else {
cx.propagate()
}
}
}
impl ContextMenuItem {
@ -185,6 +215,22 @@ impl Render for ContextMenu {
.on_action(cx.listener(ContextMenu::select_prev))
.on_action(cx.listener(ContextMenu::confirm))
.on_action(cx.listener(ContextMenu::cancel))
.map(|mut el| {
for item in self.items.iter() {
if let ContextMenuItem::Entry {
action: Some(action),
..
} = item
{
el = el.on_boxed_action(
action,
cx.listener(ContextMenu::on_action_dispatch),
);
}
}
el
})
.on_blur(cx.listener(|this, _, cx| this.cancel(&Default::default(), cx)))
.flex_none()
.child(
List::new().children(self.items.iter().enumerate().map(
@ -196,8 +242,8 @@ impl Render for ContextMenu {
ContextMenuItem::Entry {
label,
handler,
key_binding,
icon,
action,
} => {
let handler = handler.clone();
let dismiss = cx.listener(|_, _, cx| cx.emit(DismissEvent));
@ -218,11 +264,10 @@ impl Render for ContextMenu {
.w_full()
.justify_between()
.child(label_element)
.children(
key_binding
.clone()
.map(|binding| div().ml_1().child(binding)),
),
.children(action.as_ref().and_then(|action| {
KeyBinding::for_action(&**action, cx)
.map(|binding| div().ml_1().child(binding))
})),
)
.selected(Some(ix) == self.selected_index)
.on_click(move |event, cx| {