Rework context menu's custom element API to handle clicks properly

This commit is contained in:
Kirill Bulatov 2023-12-20 18:22:39 +02:00
parent 19d1568140
commit 331af82cb1
2 changed files with 97 additions and 87 deletions

View file

@ -3,9 +3,8 @@ use editor::{Editor, EditorElement, EditorEvent, MoveToEnd};
use futures::{channel::mpsc, StreamExt}; use futures::{channel::mpsc, StreamExt};
use gpui::{ use gpui::{
actions, div, AnchorCorner, AnyElement, AppContext, Context, Div, EventEmitter, FocusHandle, actions, div, AnchorCorner, AnyElement, AppContext, Context, Div, EventEmitter, FocusHandle,
FocusableView, InteractiveElement, IntoElement, Model, ModelContext, MouseButton, FocusableView, IntoElement, Model, ModelContext, ParentElement, Render, Styled, Subscription,
ParentElement, Render, Styled, Subscription, View, ViewContext, VisualContext, WeakModel, View, ViewContext, VisualContext, WeakModel, WindowContext,
WindowContext,
}; };
use language::{LanguageServerId, LanguageServerName}; use language::{LanguageServerId, LanguageServerName};
use lsp::IoKind; use lsp::IoKind;
@ -776,47 +775,48 @@ impl Render for LspLogToolbarItemView {
); );
} }
menu = menu.custom_entry({ menu = menu.custom_entry(
let log_view = log_view.clone(); {
let log_toolbar_view = log_toolbar_view.clone(); let log_toolbar_view = log_toolbar_view.clone();
move |cx| { move |cx| {
h_stack() h_stack()
.w_full() .w_full()
.justify_between() .justify_between()
.child(Label::new(RPC_MESSAGES)) .child(Label::new(RPC_MESSAGES))
.child( .child(
Checkbox::new( div().z_index(120).child(
ix, Checkbox::new(
if row.rpc_trace_enabled { ix,
Selection::Selected if row.rpc_trace_enabled {
} else { Selection::Selected
Selection::Unselected } else {
}, Selection::Unselected
) },
.on_click( )
cx.listener_for( .on_click(cx.listener_for(
&log_toolbar_view, &log_toolbar_view,
move |view, selection, cx| { move |view, selection, cx| {
let enabled = let enabled = matches!(
matches!(selection, Selection::Selected); selection,
view.toggle_logging_for_server( Selection::Selected
row.server_id, );
enabled, view.toggle_logging_for_server(
cx, row.server_id,
); enabled,
}, cx,
);
cx.stop_propagation();
},
)),
), ),
), )
) .into_any_element()
.on_mouse_down( }
MouseButton::Left, },
cx.listener_for(&log_view, move |view, _, cx| { cx.handler_for(&log_view, move |view, cx| {
view.show_rpc_trace_for_server(row.server_id, cx); view.show_rpc_trace_for_server(row.server_id, cx);
}), }),
) );
.into_any_element()
}
});
if server_selected && row.rpc_trace_selected { if server_selected && row.rpc_trace_selected {
debug_assert_eq!( debug_assert_eq!(
Some(ix * 3 + 2), Some(ix * 3 + 2),

View file

@ -20,6 +20,7 @@ enum ContextMenuItem {
}, },
CustomEntry { CustomEntry {
entry_render: Box<dyn Fn(&mut WindowContext) -> AnyElement>, entry_render: Box<dyn Fn(&mut WindowContext) -> AnyElement>,
handler: Rc<dyn Fn(&mut WindowContext)>,
}, },
} }
@ -89,9 +90,11 @@ impl ContextMenu {
pub fn custom_entry( pub fn custom_entry(
mut self, mut self,
entry_render: impl Fn(&mut WindowContext) -> AnyElement + 'static, entry_render: impl Fn(&mut WindowContext) -> AnyElement + 'static,
handler: impl Fn(&mut WindowContext) + 'static,
) -> Self { ) -> Self {
self.items.push(ContextMenuItem::CustomEntry { self.items.push(ContextMenuItem::CustomEntry {
entry_render: Box::new(entry_render), entry_render: Box::new(entry_render),
handler: Rc::new(handler),
}); });
self self
} }
@ -117,10 +120,12 @@ impl ContextMenu {
} }
pub fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) { pub fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
if let Some(ContextMenuItem::Entry { handler, .. }) = match self.selected_index.and_then(|ix| self.items.get(ix)) {
self.selected_index.and_then(|ix| self.items.get(ix)) Some(
{ ContextMenuItem::Entry { handler, .. }
(handler)(cx) | ContextMenuItem::CustomEntry { handler, .. },
) => (handler)(cx),
_ => {}
} }
cx.emit(DismissEvent); cx.emit(DismissEvent);
@ -251,51 +256,56 @@ impl Render for ContextMenu {
}) })
.flex_none() .flex_none()
.child(List::new().children(self.items.iter_mut().enumerate().map( .child(List::new().children(self.items.iter_mut().enumerate().map(
|(ix, item)| { |(ix, item)| match item {
match item { ContextMenuItem::Separator => ListSeparator.into_any_element(),
ContextMenuItem::Separator => ListSeparator.into_any_element(), ContextMenuItem::Header(header) => {
ContextMenuItem::Header(header) => { ListSubHeader::new(header.clone()).into_any_element()
ListSubHeader::new(header.clone()).into_any_element() }
} ContextMenuItem::Entry {
ContextMenuItem::Entry { label,
label, handler,
handler, icon,
icon, action,
action, } => {
} => { let handler = handler.clone();
let handler = handler.clone();
let label_element = if let Some(icon) = icon { let label_element = if let Some(icon) = icon {
h_stack() h_stack()
.gap_1() .gap_1()
.child(Label::new(label.clone())) .child(Label::new(label.clone()))
.child(IconElement::new(*icon)) .child(IconElement::new(*icon))
.into_any_element()
} else {
Label::new(label.clone()).into_any_element()
};
ListItem::new(ix)
.inset(true)
.selected(Some(ix) == self.selected_index)
.on_click(move |_, cx| handler(cx))
.child(
h_stack()
.w_full()
.justify_between()
.child(label_element)
.children(action.as_ref().and_then(|action| {
KeyBinding::for_action(&**action, cx)
.map(|binding| div().ml_1().child(binding))
})),
)
.into_any_element() .into_any_element()
} } else {
ContextMenuItem::CustomEntry { entry_render } => ListItem::new(ix) Label::new(label.clone()).into_any_element()
};
ListItem::new(ix)
.inset(true) .inset(true)
.selected(Some(ix) == self.selected_index) .selected(Some(ix) == self.selected_index)
.on_click(move |_, cx| handler(cx))
.child(
h_stack()
.w_full()
.justify_between()
.child(label_element)
.children(action.as_ref().and_then(|action| {
KeyBinding::for_action(&**action, cx)
.map(|binding| div().ml_1().child(binding))
})),
)
.into_any_element()
}
ContextMenuItem::CustomEntry {
entry_render,
handler,
} => {
let handler = handler.clone();
ListItem::new(ix)
.inset(true)
.selected(Some(ix) == self.selected_index)
.on_click(move |_, cx| handler(cx))
.child(entry_render(cx)) .child(entry_render(cx))
.into_any_element(), .into_any_element()
} }
}, },
))), ))),