context_menu.rs

  1use crate::{
  2    h_flex, prelude::*, utils::WithRemSize, v_flex, Icon, IconName, IconSize, KeyBinding, Label,
  3    List, ListItem, ListSeparator, ListSubHeader,
  4};
  5use gpui::{
  6    px, Action, AnyElement, App, AppContext as _, DismissEvent, Entity, EventEmitter, FocusHandle,
  7    Focusable, IntoElement, Render, Subscription,
  8};
  9use menu::{SelectFirst, SelectLast, SelectNext, SelectPrev};
 10use settings::Settings;
 11use std::{rc::Rc, time::Duration};
 12use theme::ThemeSettings;
 13
 14pub enum ContextMenuItem {
 15    Separator,
 16    Header(SharedString),
 17    Label(SharedString),
 18    Entry(ContextMenuEntry),
 19    CustomEntry {
 20        entry_render: Box<dyn Fn(&mut Window, &mut App) -> AnyElement>,
 21        handler: Rc<dyn Fn(Option<&FocusHandle>, &mut Window, &mut App)>,
 22        selectable: bool,
 23    },
 24}
 25
 26impl ContextMenuItem {
 27    pub fn custom_entry(
 28        entry_render: impl Fn(&mut Window, &mut App) -> AnyElement + 'static,
 29        handler: impl Fn(&mut Window, &mut App) + 'static,
 30    ) -> Self {
 31        Self::CustomEntry {
 32            entry_render: Box::new(entry_render),
 33            handler: Rc::new(move |_, window, cx| handler(window, cx)),
 34            selectable: true,
 35        }
 36    }
 37}
 38
 39pub struct ContextMenuEntry {
 40    toggle: Option<(IconPosition, bool)>,
 41    label: SharedString,
 42    icon: Option<IconName>,
 43    icon_position: IconPosition,
 44    icon_size: IconSize,
 45    icon_color: Option<Color>,
 46    handler: Rc<dyn Fn(Option<&FocusHandle>, &mut Window, &mut App)>,
 47    action: Option<Box<dyn Action>>,
 48    disabled: bool,
 49    documentation_aside: Option<Rc<dyn Fn(&mut App) -> AnyElement>>,
 50}
 51
 52impl ContextMenuEntry {
 53    pub fn new(label: impl Into<SharedString>) -> Self {
 54        ContextMenuEntry {
 55            toggle: None,
 56            label: label.into(),
 57            icon: None,
 58            icon_position: IconPosition::Start,
 59            icon_size: IconSize::Small,
 60            icon_color: None,
 61            handler: Rc::new(|_, _, _| {}),
 62            action: None,
 63            disabled: false,
 64            documentation_aside: None,
 65        }
 66    }
 67
 68    pub fn toggleable(mut self, toggle_position: IconPosition, toggled: bool) -> Self {
 69        self.toggle = Some((toggle_position, toggled));
 70        self
 71    }
 72
 73    pub fn icon(mut self, icon: IconName) -> Self {
 74        self.icon = Some(icon);
 75        self
 76    }
 77
 78    pub fn icon_position(mut self, position: IconPosition) -> Self {
 79        self.icon_position = position;
 80        self
 81    }
 82
 83    pub fn icon_size(mut self, icon_size: IconSize) -> Self {
 84        self.icon_size = icon_size;
 85        self
 86    }
 87
 88    pub fn icon_color(mut self, icon_color: Color) -> Self {
 89        self.icon_color = Some(icon_color);
 90        self
 91    }
 92
 93    pub fn toggle(mut self, toggle_position: IconPosition, toggled: bool) -> Self {
 94        self.toggle = Some((toggle_position, toggled));
 95        self
 96    }
 97
 98    pub fn action(mut self, action: Box<dyn Action>) -> Self {
 99        self.action = Some(action);
100        self
101    }
102
103    pub fn handler(mut self, handler: impl Fn(&mut Window, &mut App) + 'static) -> Self {
104        self.handler = Rc::new(move |_, window, cx| handler(window, cx));
105        self
106    }
107
108    pub fn disabled(mut self, disabled: bool) -> Self {
109        self.disabled = disabled;
110        self
111    }
112
113    pub fn documentation_aside(
114        mut self,
115        element: impl Fn(&mut App) -> AnyElement + 'static,
116    ) -> Self {
117        self.documentation_aside = Some(Rc::new(element));
118        self
119    }
120}
121
122impl From<ContextMenuEntry> for ContextMenuItem {
123    fn from(entry: ContextMenuEntry) -> Self {
124        ContextMenuItem::Entry(entry)
125    }
126}
127
128pub struct ContextMenu {
129    items: Vec<ContextMenuItem>,
130    focus_handle: FocusHandle,
131    action_context: Option<FocusHandle>,
132    selected_index: Option<usize>,
133    delayed: bool,
134    clicked: bool,
135    _on_blur_subscription: Subscription,
136    keep_open_on_confirm: bool,
137    documentation_aside: Option<(usize, Rc<dyn Fn(&mut App) -> AnyElement>)>,
138}
139
140impl Focusable for ContextMenu {
141    fn focus_handle(&self, _cx: &App) -> FocusHandle {
142        self.focus_handle.clone()
143    }
144}
145
146impl EventEmitter<DismissEvent> for ContextMenu {}
147
148impl FluentBuilder for ContextMenu {}
149
150impl ContextMenu {
151    pub fn build(
152        window: &mut Window,
153        cx: &mut App,
154        f: impl FnOnce(Self, &mut Window, &mut Context<Self>) -> Self,
155    ) -> Entity<Self> {
156        cx.new(|cx| {
157            let focus_handle = cx.focus_handle();
158            let _on_blur_subscription = cx.on_blur(
159                &focus_handle,
160                window,
161                |this: &mut ContextMenu, window, cx| this.cancel(&menu::Cancel, window, cx),
162            );
163            window.refresh();
164            f(
165                Self {
166                    items: Default::default(),
167                    focus_handle,
168                    action_context: None,
169                    selected_index: None,
170                    delayed: false,
171                    clicked: false,
172                    _on_blur_subscription,
173                    keep_open_on_confirm: false,
174                    documentation_aside: None,
175                },
176                window,
177                cx,
178            )
179        })
180    }
181
182    pub fn context(mut self, focus: FocusHandle) -> Self {
183        self.action_context = Some(focus);
184        self
185    }
186
187    pub fn header(mut self, title: impl Into<SharedString>) -> Self {
188        self.items.push(ContextMenuItem::Header(title.into()));
189        self
190    }
191
192    pub fn separator(mut self) -> Self {
193        self.items.push(ContextMenuItem::Separator);
194        self
195    }
196
197    pub fn extend<I: Into<ContextMenuItem>>(mut self, items: impl IntoIterator<Item = I>) -> Self {
198        self.items.extend(items.into_iter().map(Into::into));
199        self
200    }
201
202    pub fn item(mut self, item: impl Into<ContextMenuItem>) -> Self {
203        self.items.push(item.into());
204        self
205    }
206
207    pub fn entry(
208        mut self,
209        label: impl Into<SharedString>,
210        action: Option<Box<dyn Action>>,
211        handler: impl Fn(&mut Window, &mut App) + 'static,
212    ) -> Self {
213        self.items.push(ContextMenuItem::Entry(ContextMenuEntry {
214            toggle: None,
215            label: label.into(),
216            handler: Rc::new(move |_, window, cx| handler(window, cx)),
217            icon: None,
218            icon_position: IconPosition::End,
219            icon_size: IconSize::Small,
220            icon_color: None,
221            action,
222            disabled: false,
223            documentation_aside: None,
224        }));
225        self
226    }
227
228    pub fn toggleable_entry(
229        mut self,
230        label: impl Into<SharedString>,
231        toggled: bool,
232        position: IconPosition,
233        action: Option<Box<dyn Action>>,
234        handler: impl Fn(&mut Window, &mut App) + 'static,
235    ) -> Self {
236        self.items.push(ContextMenuItem::Entry(ContextMenuEntry {
237            toggle: Some((position, toggled)),
238            label: label.into(),
239            handler: Rc::new(move |_, window, cx| handler(window, cx)),
240            icon: None,
241            icon_position: position,
242            icon_size: IconSize::Small,
243            icon_color: None,
244            action,
245            disabled: false,
246            documentation_aside: None,
247        }));
248        self
249    }
250
251    pub fn custom_row(
252        mut self,
253        entry_render: impl Fn(&mut Window, &mut App) -> AnyElement + 'static,
254    ) -> Self {
255        self.items.push(ContextMenuItem::CustomEntry {
256            entry_render: Box::new(entry_render),
257            handler: Rc::new(|_, _, _| {}),
258            selectable: false,
259        });
260        self
261    }
262
263    pub fn custom_entry(
264        mut self,
265        entry_render: impl Fn(&mut Window, &mut App) -> AnyElement + 'static,
266        handler: impl Fn(&mut Window, &mut App) + 'static,
267    ) -> Self {
268        self.items.push(ContextMenuItem::CustomEntry {
269            entry_render: Box::new(entry_render),
270            handler: Rc::new(move |_, window, cx| handler(window, cx)),
271            selectable: true,
272        });
273        self
274    }
275
276    pub fn label(mut self, label: impl Into<SharedString>) -> Self {
277        self.items.push(ContextMenuItem::Label(label.into()));
278        self
279    }
280
281    pub fn action(mut self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self {
282        self.items.push(ContextMenuItem::Entry(ContextMenuEntry {
283            toggle: None,
284            label: label.into(),
285            action: Some(action.boxed_clone()),
286            handler: Rc::new(move |context, window, cx| {
287                if let Some(context) = &context {
288                    window.focus(context);
289                }
290                window.dispatch_action(action.boxed_clone(), cx);
291            }),
292            icon: None,
293            icon_position: IconPosition::End,
294            icon_size: IconSize::Small,
295            icon_color: None,
296            disabled: false,
297            documentation_aside: None,
298        }));
299        self
300    }
301
302    pub fn disabled_action(
303        mut self,
304        label: impl Into<SharedString>,
305        action: Box<dyn Action>,
306    ) -> Self {
307        self.items.push(ContextMenuItem::Entry(ContextMenuEntry {
308            toggle: None,
309            label: label.into(),
310            action: Some(action.boxed_clone()),
311            handler: Rc::new(move |context, window, cx| {
312                if let Some(context) = &context {
313                    window.focus(context);
314                }
315                window.dispatch_action(action.boxed_clone(), cx);
316            }),
317            icon: None,
318            icon_size: IconSize::Small,
319            icon_position: IconPosition::End,
320            icon_color: None,
321            disabled: true,
322            documentation_aside: None,
323        }));
324        self
325    }
326
327    pub fn link(mut self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self {
328        self.items.push(ContextMenuItem::Entry(ContextMenuEntry {
329            toggle: None,
330            label: label.into(),
331            action: Some(action.boxed_clone()),
332            handler: Rc::new(move |_, window, cx| window.dispatch_action(action.boxed_clone(), cx)),
333            icon: Some(IconName::ArrowUpRight),
334            icon_size: IconSize::XSmall,
335            icon_position: IconPosition::End,
336            icon_color: None,
337            disabled: false,
338            documentation_aside: None,
339        }));
340        self
341    }
342
343    pub fn keep_open_on_confirm(mut self) -> Self {
344        self.keep_open_on_confirm = true;
345        self
346    }
347
348    pub fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
349        let context = self.action_context.as_ref();
350        if let Some(
351            ContextMenuItem::Entry(ContextMenuEntry {
352                handler,
353                disabled: false,
354                ..
355            })
356            | ContextMenuItem::CustomEntry { handler, .. },
357        ) = self.selected_index.and_then(|ix| self.items.get(ix))
358        {
359            (handler)(context, window, cx)
360        }
361
362        if !self.keep_open_on_confirm {
363            cx.emit(DismissEvent);
364        }
365    }
366
367    pub fn cancel(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
368        cx.emit(DismissEvent);
369        cx.emit(DismissEvent);
370    }
371
372    fn select_first(&mut self, _: &SelectFirst, _: &mut Window, cx: &mut Context<Self>) {
373        if let Some(ix) = self.items.iter().position(|item| item.is_selectable()) {
374            self.select_index(ix);
375        }
376        cx.notify();
377    }
378
379    pub fn select_last(&mut self) -> Option<usize> {
380        for (ix, item) in self.items.iter().enumerate().rev() {
381            if item.is_selectable() {
382                return self.select_index(ix);
383            }
384        }
385        None
386    }
387
388    fn handle_select_last(&mut self, _: &SelectLast, _: &mut Window, cx: &mut Context<Self>) {
389        if self.select_last().is_some() {
390            cx.notify();
391        }
392    }
393
394    fn select_next(&mut self, _: &SelectNext, window: &mut Window, cx: &mut Context<Self>) {
395        if let Some(ix) = self.selected_index {
396            let next_index = ix + 1;
397            if self.items.len() <= next_index {
398                self.select_first(&SelectFirst, window, cx);
399            } else {
400                for (ix, item) in self.items.iter().enumerate().skip(next_index) {
401                    if item.is_selectable() {
402                        self.select_index(ix);
403                        cx.notify();
404                        break;
405                    }
406                }
407            }
408        } else {
409            self.select_first(&SelectFirst, window, cx);
410        }
411    }
412
413    pub fn select_prev(&mut self, _: &SelectPrev, window: &mut Window, cx: &mut Context<Self>) {
414        if let Some(ix) = self.selected_index {
415            if ix == 0 {
416                self.handle_select_last(&SelectLast, window, cx);
417            } else {
418                for (ix, item) in self.items.iter().enumerate().take(ix).rev() {
419                    if item.is_selectable() {
420                        self.select_index(ix);
421                        cx.notify();
422                        break;
423                    }
424                }
425            }
426        } else {
427            self.handle_select_last(&SelectLast, window, cx);
428        }
429    }
430
431    fn select_index(&mut self, ix: usize) -> Option<usize> {
432        self.documentation_aside = None;
433        let item = self.items.get(ix)?;
434        if item.is_selectable() {
435            self.selected_index = Some(ix);
436            if let ContextMenuItem::Entry(entry) = item {
437                if let Some(callback) = &entry.documentation_aside {
438                    self.documentation_aside = Some((ix, callback.clone()));
439                }
440            }
441        }
442        Some(ix)
443    }
444
445    pub fn on_action_dispatch(
446        &mut self,
447        dispatched: &dyn Action,
448        window: &mut Window,
449        cx: &mut Context<Self>,
450    ) {
451        if self.clicked {
452            cx.propagate();
453            return;
454        }
455
456        if let Some(ix) = self.items.iter().position(|item| {
457            if let ContextMenuItem::Entry(ContextMenuEntry {
458                action: Some(action),
459                disabled: false,
460                ..
461            }) = item
462            {
463                action.partial_eq(dispatched)
464            } else {
465                false
466            }
467        }) {
468            self.select_index(ix);
469            self.delayed = true;
470            cx.notify();
471            let action = dispatched.boxed_clone();
472            cx.spawn_in(window, |this, mut cx| async move {
473                cx.background_executor()
474                    .timer(Duration::from_millis(50))
475                    .await;
476                cx.update(|window, cx| {
477                    this.update(cx, |this, cx| {
478                        this.cancel(&menu::Cancel, window, cx);
479                        window.dispatch_action(action, cx);
480                    })
481                })
482            })
483            .detach_and_log_err(cx);
484        } else {
485            cx.propagate()
486        }
487    }
488
489    pub fn on_blur_subscription(mut self, new_subscription: Subscription) -> Self {
490        self._on_blur_subscription = new_subscription;
491        self
492    }
493}
494
495impl ContextMenuItem {
496    fn is_selectable(&self) -> bool {
497        match self {
498            ContextMenuItem::Header(_)
499            | ContextMenuItem::Separator
500            | ContextMenuItem::Label { .. } => false,
501            ContextMenuItem::Entry(ContextMenuEntry { disabled, .. }) => !disabled,
502            ContextMenuItem::CustomEntry { selectable, .. } => *selectable,
503        }
504    }
505}
506
507impl Render for ContextMenu {
508    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
509        let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
510        let window_size = window.viewport_size();
511        let rem_size = window.rem_size();
512        let is_wide_window = window_size.width / rem_size > rems_from_px(800.).0;
513
514        let aside = self
515            .documentation_aside
516            .as_ref()
517            .map(|(_, callback)| callback.clone());
518
519        h_flex()
520            .when(is_wide_window, |this| {this.flex_row()})
521            .when(!is_wide_window, |this| {this.flex_col()})
522            .w_full()
523            .items_start()
524            .gap_1()
525            .child(
526                div().children(aside.map(|aside|
527                    WithRemSize::new(ui_font_size)
528                        .occlude()
529                        .elevation_2(cx)
530                        .p_2()
531                        .overflow_hidden()
532                        .when(is_wide_window, |this| {this.max_w_96()})
533                        .when(!is_wide_window, |this| {this.max_w_48()})
534                        .child(aside(cx))
535                ))
536            )
537            .child(
538                WithRemSize::new(ui_font_size)
539                    .occlude()
540                    .elevation_2(cx)
541                    .flex()
542                    .flex_row()
543                    .child(
544                        v_flex()
545                            .id("context-menu")
546                            .min_w(px(200.))
547                            .max_h(vh(0.75, window))
548                            .flex_1()
549                            .overflow_y_scroll()
550                            .track_focus(&self.focus_handle(cx))
551                            .on_mouse_down_out(cx.listener(|this, _, window, cx| {
552                                this.cancel(&menu::Cancel, window, cx)
553                            }))
554                            .key_context("menu")
555                            .on_action(cx.listener(ContextMenu::select_first))
556                            .on_action(cx.listener(ContextMenu::handle_select_last))
557                            .on_action(cx.listener(ContextMenu::select_next))
558                            .on_action(cx.listener(ContextMenu::select_prev))
559                            .on_action(cx.listener(ContextMenu::confirm))
560                            .on_action(cx.listener(ContextMenu::cancel))
561                            .when(!self.delayed, |mut el| {
562                                for item in self.items.iter() {
563                                    if let ContextMenuItem::Entry(ContextMenuEntry {
564                                        action: Some(action),
565                                        disabled: false,
566                                        ..
567                                    }) = item
568                                    {
569                                        el = el.on_boxed_action(
570                                            &**action,
571                                            cx.listener(ContextMenu::on_action_dispatch),
572                                        );
573                                    }
574                                }
575                                el
576                            })
577                            .child(List::new().children(self.items.iter_mut().enumerate().map(
578                                |(ix, item)| {
579                                    match item {
580                                        ContextMenuItem::Separator => {
581                                            ListSeparator.into_any_element()
582                                        }
583                                        ContextMenuItem::Header(header) => {
584                                            ListSubHeader::new(header.clone())
585                                                .inset(true)
586                                                .into_any_element()
587                                        }
588                                        ContextMenuItem::Label(label) => ListItem::new(ix)
589                                            .inset(true)
590                                            .disabled(true)
591                                            .child(Label::new(label.clone()))
592                                            .into_any_element(),
593                                        ContextMenuItem::Entry(ContextMenuEntry {
594                                            toggle,
595                                            label,
596                                            handler,
597                                            icon,
598                                            icon_position,
599                                            icon_size,
600                                            icon_color,
601                                            action,
602                                            disabled,
603                                            documentation_aside,
604                                        }) => {
605                                            let handler = handler.clone();
606                                            let menu = cx.entity().downgrade();
607
608                                            let icon_color = if *disabled {
609                                                Color::Muted
610                                            } else if toggle.is_some() {
611                                                icon_color.unwrap_or(Color::Accent)
612                                            } else {
613                                                icon_color.unwrap_or(Color::Default)
614                                            };
615
616                                            let label_color = if *disabled {
617                                                Color::Disabled
618                                            } else {
619                                                Color::Default
620                                            };
621
622                                            let label_element = if let Some(icon_name) = icon {
623                                                h_flex()
624                                                    .gap_1p5()
625                                                    .when(
626                                                        *icon_position == IconPosition::Start && toggle.is_none(),
627                                                        |flex| {
628                                                            flex.child(
629                                                                Icon::new(*icon_name)
630                                                                    .size(*icon_size)
631                                                                    .color(icon_color),
632                                                            )
633                                                        },
634                                                    )
635                                                    .child(
636                                                        Label::new(label.clone())
637                                                            .color(label_color),
638                                                    )
639                                                    .when(
640                                                        *icon_position == IconPosition::End,
641                                                        |flex| {
642                                                            flex.child(
643                                                                Icon::new(*icon_name)
644                                                                    .size(*icon_size)
645                                                                    .color(icon_color),
646                                                            )
647                                                        },
648                                                    )
649                                                    .into_any_element()
650                                            } else {
651                                                Label::new(label.clone())
652                                                    .color(label_color)
653                                                    .into_any_element()
654                                            };
655
656                                            let documentation_aside_callback =
657                                                documentation_aside.clone();
658
659                                            div()
660                                                .id(("context-menu-child", ix))
661                                                .when_some(
662                                                    documentation_aside_callback.clone(),
663                                                    |this, documentation_aside_callback| {
664                                                        this.occlude().on_hover(cx.listener(
665                                                            move |menu, hovered, _, cx| {
666                                                                if *hovered {
667                                                                    menu.documentation_aside = Some((ix, documentation_aside_callback.clone()));
668                                                                    cx.notify();
669                                                                } else if matches!(menu.documentation_aside, Some((id, _)) if id == ix) {
670                                                                    menu.documentation_aside = None;
671                                                                    cx.notify();
672                                                                }
673                                                            },
674                                                        ))
675                                                    },
676                                                )
677                                                .child(
678                                                    ListItem::new(ix)
679                                                        .inset(true)
680                                                        .disabled(*disabled)
681                                                        .toggle_state(
682                                                            Some(ix) == self.selected_index,
683                                                        )
684                                                        .when_some(
685                                                            *toggle,
686                                                            |list_item, (position, toggled)| {
687                                                                let contents =
688                                                                    div().flex_none().child(
689                                                                        Icon::new(icon.unwrap_or(IconName::Check))
690                                                                            .color(icon_color)
691                                                                            .size(*icon_size)
692                                                                    )
693                                                                    .when(!toggled, |contents|
694                                                                        contents.invisible()
695                                                                    );
696
697                                                                match position {
698                                                                    IconPosition::Start => {
699                                                                        list_item
700                                                                            .start_slot(contents)
701                                                                    }
702                                                                    IconPosition::End => {
703                                                                        list_item.end_slot(contents)
704                                                                    }
705                                                                }
706                                                            },
707                                                        )
708                                                        .child(
709                                                            h_flex()
710                                                                .w_full()
711                                                                .justify_between()
712                                                                .child(label_element)
713                                                                .debug_selector(|| {
714                                                                    format!("MENU_ITEM-{}", label)
715                                                                })
716                                                                .children(
717                                                                    action.as_ref().and_then(
718                                                                        |action| {
719                                                                            self.action_context
720                                                                    .as_ref()
721                                                                    .map(|focus| {
722                                                                        KeyBinding::for_action_in(
723                                                                            &**action, focus,
724                                                                            window,
725                                                                            cx
726                                                                        )
727                                                                    })
728                                                                    .unwrap_or_else(|| {
729                                                                        KeyBinding::for_action(
730                                                                            &**action, window, cx
731                                                                        )
732                                                                    })
733                                                                    .map(|binding| {
734                                                                        div().ml_4().child(binding)
735                                                                            .when(*disabled && documentation_aside_callback.is_some(), |parent| {
736                                                                                parent.invisible()
737                                                                            })
738                                                                    })
739                                                                        },
740                                                                    ),
741                                                                )
742                                                                .when(*disabled && documentation_aside_callback.is_some(), |parent| {
743                                                                    parent.child(Icon::new(IconName::Info).size(IconSize::XSmall).color(Color::Muted))
744                                                                }),
745                                                        )
746                                                        .on_click({
747                                                            let context =
748                                                                self.action_context.clone();
749                                                            move |_, window, cx| {
750                                                                handler(
751                                                                    context.as_ref(),
752                                                                    window,
753                                                                    cx,
754                                                                );
755                                                                menu.update(cx, |menu, cx| {
756                                                                    menu.clicked = true;
757                                                                    cx.emit(DismissEvent);
758                                                                })
759                                                                .ok();
760                                                            }
761                                                        }),
762                                                )
763                                                .into_any_element()
764                                        }
765                                        ContextMenuItem::CustomEntry {
766                                            entry_render,
767                                            handler,
768                                            selectable,
769                                        } => {
770                                            let handler = handler.clone();
771                                            let menu = cx.entity().downgrade();
772                                            let selectable = *selectable;
773                                            ListItem::new(ix)
774                                                .inset(true)
775                                                .toggle_state(if selectable {
776                                                    Some(ix) == self.selected_index
777                                                } else {
778                                                    false
779                                                })
780                                                .selectable(selectable)
781                                                .when(selectable, |item| {
782                                                    item.on_click({
783                                                        let context = self.action_context.clone();
784                                                        move |_, window, cx| {
785                                                            handler(context.as_ref(), window, cx);
786                                                            menu.update(cx, |menu, cx| {
787                                                                menu.clicked = true;
788                                                                cx.emit(DismissEvent);
789                                                            })
790                                                            .ok();
791                                                        }
792                                                    })
793                                                })
794                                                .child(entry_render(window, cx))
795                                                .into_any_element()
796                                        }
797                                    }
798                                },
799                            )))
800                    ),
801            )
802    }
803}