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, SelectPrevious};
 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_previous(
414        &mut self,
415        _: &SelectPrevious,
416        window: &mut Window,
417        cx: &mut Context<Self>,
418    ) {
419        if let Some(ix) = self.selected_index {
420            if ix == 0 {
421                self.handle_select_last(&SelectLast, window, cx);
422            } else {
423                for (ix, item) in self.items.iter().enumerate().take(ix).rev() {
424                    if item.is_selectable() {
425                        self.select_index(ix);
426                        cx.notify();
427                        break;
428                    }
429                }
430            }
431        } else {
432            self.handle_select_last(&SelectLast, window, cx);
433        }
434    }
435
436    fn select_index(&mut self, ix: usize) -> Option<usize> {
437        self.documentation_aside = None;
438        let item = self.items.get(ix)?;
439        if item.is_selectable() {
440            self.selected_index = Some(ix);
441            if let ContextMenuItem::Entry(entry) = item {
442                if let Some(callback) = &entry.documentation_aside {
443                    self.documentation_aside = Some((ix, callback.clone()));
444                }
445            }
446        }
447        Some(ix)
448    }
449
450    pub fn on_action_dispatch(
451        &mut self,
452        dispatched: &dyn Action,
453        window: &mut Window,
454        cx: &mut Context<Self>,
455    ) {
456        if self.clicked {
457            cx.propagate();
458            return;
459        }
460
461        if let Some(ix) = self.items.iter().position(|item| {
462            if let ContextMenuItem::Entry(ContextMenuEntry {
463                action: Some(action),
464                disabled: false,
465                ..
466            }) = item
467            {
468                action.partial_eq(dispatched)
469            } else {
470                false
471            }
472        }) {
473            self.select_index(ix);
474            self.delayed = true;
475            cx.notify();
476            let action = dispatched.boxed_clone();
477            cx.spawn_in(window, |this, mut cx| async move {
478                cx.background_executor()
479                    .timer(Duration::from_millis(50))
480                    .await;
481                cx.update(|window, cx| {
482                    this.update(cx, |this, cx| {
483                        this.cancel(&menu::Cancel, window, cx);
484                        window.dispatch_action(action, cx);
485                    })
486                })
487            })
488            .detach_and_log_err(cx);
489        } else {
490            cx.propagate()
491        }
492    }
493
494    pub fn on_blur_subscription(mut self, new_subscription: Subscription) -> Self {
495        self._on_blur_subscription = new_subscription;
496        self
497    }
498}
499
500impl ContextMenuItem {
501    fn is_selectable(&self) -> bool {
502        match self {
503            ContextMenuItem::Header(_)
504            | ContextMenuItem::Separator
505            | ContextMenuItem::Label { .. } => false,
506            ContextMenuItem::Entry(ContextMenuEntry { disabled, .. }) => !disabled,
507            ContextMenuItem::CustomEntry { selectable, .. } => *selectable,
508        }
509    }
510}
511
512impl Render for ContextMenu {
513    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
514        let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
515        let window_size = window.viewport_size();
516        let rem_size = window.rem_size();
517        let is_wide_window = window_size.width / rem_size > rems_from_px(800.).0;
518
519        let aside = self
520            .documentation_aside
521            .as_ref()
522            .map(|(_, callback)| callback.clone());
523
524        h_flex()
525            .when(is_wide_window, |this| {this.flex_row()})
526            .when(!is_wide_window, |this| {this.flex_col()})
527            .w_full()
528            .items_start()
529            .gap_1()
530            .child(
531                div().children(aside.map(|aside|
532                    WithRemSize::new(ui_font_size)
533                        .occlude()
534                        .elevation_2(cx)
535                        .p_2()
536                        .overflow_hidden()
537                        .when(is_wide_window, |this| {this.max_w_96()})
538                        .when(!is_wide_window, |this| {this.max_w_48()})
539                        .child(aside(cx))
540                ))
541            )
542            .child(
543                WithRemSize::new(ui_font_size)
544                    .occlude()
545                    .elevation_2(cx)
546                    .flex()
547                    .flex_row()
548                    .child(
549                        v_flex()
550                            .id("context-menu")
551                            .min_w(px(200.))
552                            .max_h(vh(0.75, window))
553                            .flex_1()
554                            .overflow_y_scroll()
555                            .track_focus(&self.focus_handle(cx))
556                            .on_mouse_down_out(cx.listener(|this, _, window, cx| {
557                                this.cancel(&menu::Cancel, window, cx)
558                            }))
559                            .key_context("menu")
560                            .on_action(cx.listener(ContextMenu::select_first))
561                            .on_action(cx.listener(ContextMenu::handle_select_last))
562                            .on_action(cx.listener(ContextMenu::select_next))
563                            .on_action(cx.listener(ContextMenu::select_previous))
564                            .on_action(cx.listener(ContextMenu::confirm))
565                            .on_action(cx.listener(ContextMenu::cancel))
566                            .when(!self.delayed, |mut el| {
567                                for item in self.items.iter() {
568                                    if let ContextMenuItem::Entry(ContextMenuEntry {
569                                        action: Some(action),
570                                        disabled: false,
571                                        ..
572                                    }) = item
573                                    {
574                                        el = el.on_boxed_action(
575                                            &**action,
576                                            cx.listener(ContextMenu::on_action_dispatch),
577                                        );
578                                    }
579                                }
580                                el
581                            })
582                            .child(List::new().children(self.items.iter_mut().enumerate().map(
583                                |(ix, item)| {
584                                    match item {
585                                        ContextMenuItem::Separator => {
586                                            ListSeparator.into_any_element()
587                                        }
588                                        ContextMenuItem::Header(header) => {
589                                            ListSubHeader::new(header.clone())
590                                                .inset(true)
591                                                .into_any_element()
592                                        }
593                                        ContextMenuItem::Label(label) => ListItem::new(ix)
594                                            .inset(true)
595                                            .disabled(true)
596                                            .child(Label::new(label.clone()))
597                                            .into_any_element(),
598                                        ContextMenuItem::Entry(ContextMenuEntry {
599                                            toggle,
600                                            label,
601                                            handler,
602                                            icon,
603                                            icon_position,
604                                            icon_size,
605                                            icon_color,
606                                            action,
607                                            disabled,
608                                            documentation_aside,
609                                        }) => {
610                                            let handler = handler.clone();
611                                            let menu = cx.entity().downgrade();
612
613                                            let icon_color = if *disabled {
614                                                Color::Muted
615                                            } else if toggle.is_some() {
616                                                icon_color.unwrap_or(Color::Accent)
617                                            } else {
618                                                icon_color.unwrap_or(Color::Default)
619                                            };
620
621                                            let label_color = if *disabled {
622                                                Color::Disabled
623                                            } else {
624                                                Color::Default
625                                            };
626
627                                            let label_element = if let Some(icon_name) = icon {
628                                                h_flex()
629                                                    .gap_1p5()
630                                                    .when(
631                                                        *icon_position == IconPosition::Start && toggle.is_none(),
632                                                        |flex| {
633                                                            flex.child(
634                                                                Icon::new(*icon_name)
635                                                                    .size(*icon_size)
636                                                                    .color(icon_color),
637                                                            )
638                                                        },
639                                                    )
640                                                    .child(
641                                                        Label::new(label.clone())
642                                                            .color(label_color),
643                                                    )
644                                                    .when(
645                                                        *icon_position == IconPosition::End,
646                                                        |flex| {
647                                                            flex.child(
648                                                                Icon::new(*icon_name)
649                                                                    .size(*icon_size)
650                                                                    .color(icon_color),
651                                                            )
652                                                        },
653                                                    )
654                                                    .into_any_element()
655                                            } else {
656                                                Label::new(label.clone())
657                                                    .color(label_color)
658                                                    .into_any_element()
659                                            };
660
661                                            let documentation_aside_callback =
662                                                documentation_aside.clone();
663
664                                            div()
665                                                .id(("context-menu-child", ix))
666                                                .when_some(
667                                                    documentation_aside_callback.clone(),
668                                                    |this, documentation_aside_callback| {
669                                                        this.occlude().on_hover(cx.listener(
670                                                            move |menu, hovered, _, cx| {
671                                                                if *hovered {
672                                                                    menu.documentation_aside = Some((ix, documentation_aside_callback.clone()));
673                                                                    cx.notify();
674                                                                } else if matches!(menu.documentation_aside, Some((id, _)) if id == ix) {
675                                                                    menu.documentation_aside = None;
676                                                                    cx.notify();
677                                                                }
678                                                            },
679                                                        ))
680                                                    },
681                                                )
682                                                .child(
683                                                    ListItem::new(ix)
684                                                        .inset(true)
685                                                        .disabled(*disabled)
686                                                        .toggle_state(
687                                                            Some(ix) == self.selected_index,
688                                                        )
689                                                        .when_some(
690                                                            *toggle,
691                                                            |list_item, (position, toggled)| {
692                                                                let contents =
693                                                                    div().flex_none().child(
694                                                                        Icon::new(icon.unwrap_or(IconName::Check))
695                                                                            .color(icon_color)
696                                                                            .size(*icon_size)
697                                                                    )
698                                                                    .when(!toggled, |contents|
699                                                                        contents.invisible()
700                                                                    );
701
702                                                                match position {
703                                                                    IconPosition::Start => {
704                                                                        list_item
705                                                                            .start_slot(contents)
706                                                                    }
707                                                                    IconPosition::End => {
708                                                                        list_item.end_slot(contents)
709                                                                    }
710                                                                }
711                                                            },
712                                                        )
713                                                        .child(
714                                                            h_flex()
715                                                                .w_full()
716                                                                .justify_between()
717                                                                .child(label_element)
718                                                                .debug_selector(|| {
719                                                                    format!("MENU_ITEM-{}", label)
720                                                                })
721                                                                .children(
722                                                                    action.as_ref().and_then(
723                                                                        |action| {
724                                                                            self.action_context
725                                                                    .as_ref()
726                                                                    .map(|focus| {
727                                                                        KeyBinding::for_action_in(
728                                                                            &**action, focus,
729                                                                            window,
730                                                                            cx
731                                                                        )
732                                                                    })
733                                                                    .unwrap_or_else(|| {
734                                                                        KeyBinding::for_action(
735                                                                            &**action, window, cx
736                                                                        )
737                                                                    })
738                                                                    .map(|binding| {
739                                                                        div().ml_4().child(binding)
740                                                                            .when(*disabled && documentation_aside_callback.is_some(), |parent| {
741                                                                                parent.invisible()
742                                                                            })
743                                                                    })
744                                                                        },
745                                                                    ),
746                                                                )
747                                                                .when(*disabled && documentation_aside_callback.is_some(), |parent| {
748                                                                    parent.child(Icon::new(IconName::Info).size(IconSize::XSmall).color(Color::Muted))
749                                                                }),
750                                                        )
751                                                        .on_click({
752                                                            let context =
753                                                                self.action_context.clone();
754                                                            move |_, window, cx| {
755                                                                handler(
756                                                                    context.as_ref(),
757                                                                    window,
758                                                                    cx,
759                                                                );
760                                                                menu.update(cx, |menu, cx| {
761                                                                    menu.clicked = true;
762                                                                    cx.emit(DismissEvent);
763                                                                })
764                                                                .ok();
765                                                            }
766                                                        }),
767                                                )
768                                                .into_any_element()
769                                        }
770                                        ContextMenuItem::CustomEntry {
771                                            entry_render,
772                                            handler,
773                                            selectable,
774                                        } => {
775                                            let handler = handler.clone();
776                                            let menu = cx.entity().downgrade();
777                                            let selectable = *selectable;
778                                            ListItem::new(ix)
779                                                .inset(true)
780                                                .toggle_state(if selectable {
781                                                    Some(ix) == self.selected_index
782                                                } else {
783                                                    false
784                                                })
785                                                .selectable(selectable)
786                                                .when(selectable, |item| {
787                                                    item.on_click({
788                                                        let context = self.action_context.clone();
789                                                        move |_, window, cx| {
790                                                            handler(context.as_ref(), window, cx);
791                                                            menu.update(cx, |menu, cx| {
792                                                                menu.clicked = true;
793                                                                cx.emit(DismissEvent);
794                                                            })
795                                                            .ok();
796                                                        }
797                                                    })
798                                                })
799                                                .child(entry_render(window, cx))
800                                                .into_any_element()
801                                        }
802                                    }
803                                },
804                            )))
805                    ),
806            )
807    }
808}