use crate::{ h_flex, prelude::*, utils::WithRemSize, v_flex, Icon, IconName, IconSize, KeyBinding, Label, List, ListItem, ListSeparator, ListSubHeader,};use gpui::{ px, Action, AnyElement, App, AppContext as _, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, IntoElement, Render, Subscription,};use menu::{SelectFirst, SelectLast, SelectNext, SelectPrevious};use settings::Settings;use std::{rc::Rc, time::Duration};use theme::ThemeSettings; pub enum ContextMenuItem { Separator, Header(SharedString), Label(SharedString), Entry(ContextMenuEntry), CustomEntry { entry_render: Box<dyn Fn(&mut Window, &mut App) -> AnyElement>, handler: Rc<dyn Fn(Option<&FocusHandle>, &mut Window, &mut App)>, selectable: bool, },} impl ContextMenuItem { pub fn custom_entry( entry_render: impl Fn(&mut Window, &mut App) -> AnyElement + 'static, handler: impl Fn(&mut Window, &mut App) + 'static, ) -> Self { Self::CustomEntry { entry_render: Box::new(entry_render), handler: Rc::new(move |_, window, cx| handler(window, cx)), selectable: true, } }} pub struct ContextMenuEntry { toggle: Option<(IconPosition, bool)>, label: SharedString, icon: Option<IconName>, icon_position: IconPosition, icon_size: IconSize, icon_color: Option<Color>, handler: Rc<dyn Fn(Option<&FocusHandle>, &mut Window, &mut App)>, action: Option<Box<dyn Action>>, disabled: bool, documentation_aside: Option<Rc<dyn Fn(&mut App) -> AnyElement>>,} impl ContextMenuEntry { pub fn new(label: impl Into<SharedString>) -> Self { ContextMenuEntry { toggle: None, label: label.into(), icon: None, icon_position: IconPosition::Start, icon_size: IconSize::Small, icon_color: None, handler: Rc::new(|_, _, _| {}), action: None, disabled: false, documentation_aside: None, } } pub fn toggleable(mut self, toggle_position: IconPosition, toggled: bool) -> Self { self.toggle = Some((toggle_position, toggled)); self } pub fn icon(mut self, icon: IconName) -> Self { self.icon = Some(icon); self } pub fn icon_position(mut self, position: IconPosition) -> Self { self.icon_position = position; self } pub fn icon_size(mut self, icon_size: IconSize) -> Self { self.icon_size = icon_size; self } pub fn icon_color(mut self, icon_color: Color) -> Self { self.icon_color = Some(icon_color); self } pub fn toggle(mut self, toggle_position: IconPosition, toggled: bool) -> Self { self.toggle = Some((toggle_position, toggled)); self } pub fn action(mut self, action: Box<dyn Action>) -> Self { self.action = Some(action); self } pub fn handler(mut self, handler: impl Fn(&mut Window, &mut App) + 'static) -> Self { self.handler = Rc::new(move |_, window, cx| handler(window, cx)); self } pub fn disabled(mut self, disabled: bool) -> Self { self.disabled = disabled; self } pub fn documentation_aside( mut self, element: impl Fn(&mut App) -> AnyElement + 'static, ) -> Self { self.documentation_aside = Some(Rc::new(element)); self }} impl From<ContextMenuEntry> for ContextMenuItem { fn from(entry: ContextMenuEntry) -> Self { ContextMenuItem::Entry(entry) }} pub struct ContextMenu { items: Vec<ContextMenuItem>, focus_handle: FocusHandle, action_context: Option<FocusHandle>, selected_index: Option<usize>, delayed: bool, clicked: bool, _on_blur_subscription: Subscription, keep_open_on_confirm: bool, documentation_aside: Option<(usize, Rc<dyn Fn(&mut App) -> AnyElement>)>,} impl Focusable for ContextMenu { fn focus_handle(&self, _cx: &App) -> FocusHandle { self.focus_handle.clone() }} impl EventEmitter<DismissEvent> for ContextMenu {} impl FluentBuilder for ContextMenu {} impl ContextMenu { pub fn build( window: &mut Window, cx: &mut App, f: impl FnOnce(Self, &mut Window, &mut Context<Self>) -> Self, ) -> Entity<Self> { cx.new(|cx| { let focus_handle = cx.focus_handle(); let _on_blur_subscription = cx.on_blur( &focus_handle, window, |this: &mut ContextMenu, window, cx| this.cancel(&menu::Cancel, window, cx), ); window.refresh(); f( Self { items: Default::default(), focus_handle, action_context: None, selected_index: None, delayed: false, clicked: false, _on_blur_subscription, keep_open_on_confirm: false, documentation_aside: None, }, window, cx, ) }) } pub fn context(mut self, focus: FocusHandle) -> Self { self.action_context = Some(focus); self } pub fn header(mut self, title: impl Into<SharedString>) -> Self { self.items.push(ContextMenuItem::Header(title.into())); self } pub fn separator(mut self) -> Self { self.items.push(ContextMenuItem::Separator); self } pub fn extend<I: Into<ContextMenuItem>>(mut self, items: impl IntoIterator<Item = I>) -> Self { self.items.extend(items.into_iter().map(Into::into)); self } pub fn item(mut self, item: impl Into<ContextMenuItem>) -> Self { self.items.push(item.into()); self } pub fn entry( mut self, label: impl Into<SharedString>, action: Option<Box<dyn Action>>, handler: impl Fn(&mut Window, &mut App) + 'static, ) -> Self { self.items.push(ContextMenuItem::Entry(ContextMenuEntry { toggle: None, label: label.into(), handler: Rc::new(move |_, window, cx| handler(window, cx)), icon: None, icon_position: IconPosition::End, icon_size: IconSize::Small, icon_color: None, action, disabled: false, documentation_aside: None, })); self } pub fn toggleable_entry( mut self, label: impl Into<SharedString>, toggled: bool, position: IconPosition, action: Option<Box<dyn Action>>, handler: impl Fn(&mut Window, &mut App) + 'static, ) -> Self { self.items.push(ContextMenuItem::Entry(ContextMenuEntry { toggle: Some((position, toggled)), label: label.into(), handler: Rc::new(move |_, window, cx| handler(window, cx)), icon: None, icon_position: position, icon_size: IconSize::Small, icon_color: None, action, disabled: false, documentation_aside: None, })); self } pub fn custom_row( mut self, entry_render: impl Fn(&mut Window, &mut App) -> AnyElement + 'static, ) -> Self { self.items.push(ContextMenuItem::CustomEntry { entry_render: Box::new(entry_render), handler: Rc::new(|_, _, _| {}), selectable: false, }); self } pub fn custom_entry( mut self, entry_render: impl Fn(&mut Window, &mut App) -> AnyElement + 'static, handler: impl Fn(&mut Window, &mut App) + 'static, ) -> Self { self.items.push(ContextMenuItem::CustomEntry { entry_render: Box::new(entry_render), handler: Rc::new(move |_, window, cx| handler(window, cx)), selectable: true, }); self } pub fn label(mut self, label: impl Into<SharedString>) -> Self { self.items.push(ContextMenuItem::Label(label.into())); self } pub fn action(mut self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self { self.items.push(ContextMenuItem::Entry(ContextMenuEntry { toggle: None, label: label.into(), action: Some(action.boxed_clone()), handler: Rc::new(move |context, window, cx| { if let Some(context) = &context { window.focus(context); } window.dispatch_action(action.boxed_clone(), cx); }), icon: None, icon_position: IconPosition::End, icon_size: IconSize::Small, icon_color: None, disabled: false, documentation_aside: None, })); self } pub fn disabled_action( mut self, label: impl Into<SharedString>, action: Box<dyn Action>, ) -> Self { self.items.push(ContextMenuItem::Entry(ContextMenuEntry { toggle: None, label: label.into(), action: Some(action.boxed_clone()), handler: Rc::new(move |context, window, cx| { if let Some(context) = &context { window.focus(context); } window.dispatch_action(action.boxed_clone(), cx); }), icon: None, icon_size: IconSize::Small, icon_position: IconPosition::End, icon_color: None, disabled: true, documentation_aside: None, })); self } pub fn link(mut self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self { self.items.push(ContextMenuItem::Entry(ContextMenuEntry { toggle: None, label: label.into(), action: Some(action.boxed_clone()), handler: Rc::new(move |_, window, cx| window.dispatch_action(action.boxed_clone(), cx)), icon: Some(IconName::ArrowUpRight), icon_size: IconSize::XSmall, icon_position: IconPosition::End, icon_color: None, disabled: false, documentation_aside: None, })); self } pub fn keep_open_on_confirm(mut self) -> Self { self.keep_open_on_confirm = true; self } pub fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) { let context = self.action_context.as_ref(); if let Some( ContextMenuItem::Entry(ContextMenuEntry { handler, disabled: false, .. }) | ContextMenuItem::CustomEntry { handler, .. }, ) = self.selected_index.and_then(|ix| self.items.get(ix)) { (handler)(context, window, cx) } if !self.keep_open_on_confirm { cx.emit(DismissEvent); } } pub fn cancel(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) { cx.emit(DismissEvent); cx.emit(DismissEvent); } fn select_first(&mut self, _: &SelectFirst, _: &mut Window, cx: &mut Context<Self>) { if let Some(ix) = self.items.iter().position(|item| item.is_selectable()) { self.select_index(ix); } cx.notify(); } pub fn select_last(&mut self) -> Option<usize> { for (ix, item) in self.items.iter().enumerate().rev() { if item.is_selectable() { return self.select_index(ix); } } None } fn handle_select_last(&mut self, _: &SelectLast, _: &mut Window, cx: &mut Context<Self>) { if self.select_last().is_some() { cx.notify(); } } fn select_next(&mut self, _: &SelectNext, window: &mut Window, cx: &mut Context<Self>) { if let Some(ix) = self.selected_index { let next_index = ix + 1; if self.items.len() <= next_index { self.select_first(&SelectFirst, window, cx); } else { for (ix, item) in self.items.iter().enumerate().skip(next_index) { if item.is_selectable() { self.select_index(ix); cx.notify(); break; } } } } else { self.select_first(&SelectFirst, window, cx); } } pub fn select_previous( &mut self, _: &SelectPrevious, window: &mut Window, cx: &mut Context<Self>, ) { if let Some(ix) = self.selected_index { if ix == 0 { self.handle_select_last(&SelectLast, window, cx); } else { for (ix, item) in self.items.iter().enumerate().take(ix).rev() { if item.is_selectable() { self.select_index(ix); cx.notify(); break; } } } } else { self.handle_select_last(&SelectLast, window, cx); } } fn select_index(&mut self, ix: usize) -> Option<usize> { self.documentation_aside = None; let item = self.items.get(ix)?; if item.is_selectable() { self.selected_index = Some(ix); if let ContextMenuItem::Entry(entry) = item { if let Some(callback) = &entry.documentation_aside { self.documentation_aside = Some((ix, callback.clone())); } } } Some(ix) } pub fn on_action_dispatch( &mut self, dispatched: &dyn Action, window: &mut Window, cx: &mut Context<Self>, ) { if self.clicked { cx.propagate(); return; } if let Some(ix) = self.items.iter().position(|item| { if let ContextMenuItem::Entry(ContextMenuEntry { action: Some(action), disabled: false, .. }) = item { action.partial_eq(dispatched) } else { false } }) { self.select_index(ix); self.delayed = true; cx.notify(); let action = dispatched.boxed_clone(); cx.spawn_in(window, |this, mut cx| async move { cx.background_executor() .timer(Duration::from_millis(50)) .await; cx.update(|window, cx| { this.update(cx, |this, cx| { this.cancel(&menu::Cancel, window, cx); window.dispatch_action(action, cx); }) }) }) .detach_and_log_err(cx); } else { cx.propagate() } } pub fn on_blur_subscription(mut self, new_subscription: Subscription) -> Self { self._on_blur_subscription = new_subscription; self }} impl ContextMenuItem {Lines 1–500 of 808. Load more
use crate::{ h_flex, prelude::*, utils::WithRemSize, v_flex, Icon, IconName, IconSize, KeyBinding, Label, List, ListItem, ListSeparator, ListSubHeader, }; use gpui::{ px, Action, AnyElement, App, AppContext as _, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, IntoElement, Render, Subscription, }; use menu::{SelectFirst, SelectLast, SelectNext, SelectPrevious}; use settings::Settings; use std::{rc::Rc, time::Duration}; use theme::ThemeSettings; pub enum ContextMenuItem { Separator, Header(SharedString), Label(SharedString), Entry(ContextMenuEntry), CustomEntry { entry_render: Box<dyn Fn(&mut Window, &mut App) -> AnyElement>, handler: Rc<dyn Fn(Option<&FocusHandle>, &mut Window, &mut App)>, selectable: bool, }, } impl ContextMenuItem { pub fn custom_entry( entry_render: impl Fn(&mut Window, &mut App) -> AnyElement + 'static, handler: impl Fn(&mut Window, &mut App) + 'static, ) -> Self { Self::CustomEntry { entry_render: Box::new(entry_render), handler: Rc::new(move |_, window, cx| handler(window, cx)), selectable: true, } } } pub struct ContextMenuEntry { toggle: Option<(IconPosition, bool)>, label: SharedString, icon: Option<IconName>, icon_position: IconPosition, icon_size: IconSize, icon_color: Option<Color>, handler: Rc<dyn Fn(Option<&FocusHandle>, &mut Window, &mut App)>, action: Option<Box<dyn Action>>, disabled: bool, documentation_aside: Option<Rc<dyn Fn(&mut App) -> AnyElement>>, } impl ContextMenuEntry { pub fn new(label: impl Into<SharedString>) -> Self { ContextMenuEntry { toggle: None, label: label.into(), icon: None, icon_position: IconPosition::Start, icon_size: IconSize::Small, icon_color: None, handler: Rc::new(|_, _, _| {}), action: None, disabled: false, documentation_aside: None, } } pub fn toggleable(mut self, toggle_position: IconPosition, toggled: bool) -> Self { self.toggle = Some((toggle_position, toggled)); self } pub fn icon(mut self, icon: IconName) -> Self { self.icon = Some(icon); self } pub fn icon_position(mut self, position: IconPosition) -> Self { self.icon_position = position; self } pub fn icon_size(mut self, icon_size: IconSize) -> Self { self.icon_size = icon_size; self } pub fn icon_color(mut self, icon_color: Color) -> Self { self.icon_color = Some(icon_color); self } pub fn toggle(mut self, toggle_position: IconPosition, toggled: bool) -> Self { self.toggle = Some((toggle_position, toggled)); self } pub fn action(mut self, action: Box<dyn Action>) -> Self { self.action = Some(action); self } pub fn handler(mut self, handler: impl Fn(&mut Window, &mut App) + 'static) -> Self { self.handler = Rc::new(move |_, window, cx| handler(window, cx)); self } pub fn disabled(mut self, disabled: bool) -> Self { self.disabled = disabled; self } pub fn documentation_aside( mut self, element: impl Fn(&mut App) -> AnyElement + 'static, ) -> Self { self.documentation_aside = Some(Rc::new(element)); self } } impl From<ContextMenuEntry> for ContextMenuItem { fn from(entry: ContextMenuEntry) -> Self { ContextMenuItem::Entry(entry) } } pub struct ContextMenu { items: Vec<ContextMenuItem>, focus_handle: FocusHandle, action_context: Option<FocusHandle>, selected_index: Option<usize>, delayed: bool, clicked: bool, _on_blur_subscription: Subscription, keep_open_on_confirm: bool, documentation_aside: Option<(usize, Rc<dyn Fn(&mut App) -> AnyElement>)>, } impl Focusable for ContextMenu { fn focus_handle(&self, _cx: &App) -> FocusHandle { self.focus_handle.clone() } } impl EventEmitter<DismissEvent> for ContextMenu {} impl FluentBuilder for ContextMenu {} impl ContextMenu { pub fn build( window: &mut Window, cx: &mut App, f: impl FnOnce(Self, &mut Window, &mut Context<Self>) -> Self, ) -> Entity<Self> { cx.new(|cx| { let focus_handle = cx.focus_handle(); let _on_blur_subscription = cx.on_blur( &focus_handle, window, |this: &mut ContextMenu, window, cx| this.cancel(&menu::Cancel, window, cx), ); window.refresh(); f( Self { items: Default::default(), focus_handle, action_context: None, selected_index: None, delayed: false, clicked: false, _on_blur_subscription, keep_open_on_confirm: false, documentation_aside: None, }, window, cx, ) }) } pub fn context(mut self, focus: FocusHandle) -> Self { self.action_context = Some(focus); self } pub fn header(mut self, title: impl Into<SharedString>) -> Self { self.items.push(ContextMenuItem::Header(title.into())); self } pub fn separator(mut self) -> Self { self.items.push(ContextMenuItem::Separator); self } pub fn extend<I: Into<ContextMenuItem>>(mut self, items: impl IntoIterator<Item = I>) -> Self { self.items.extend(items.into_iter().map(Into::into)); self } pub fn item(mut self, item: impl Into<ContextMenuItem>) -> Self { self.items.push(item.into()); self } pub fn entry( mut self, label: impl Into<SharedString>, action: Option<Box<dyn Action>>, handler: impl Fn(&mut Window, &mut App) + 'static, ) -> Self { self.items.push(ContextMenuItem::Entry(ContextMenuEntry { toggle: None, label: label.into(), handler: Rc::new(move |_, window, cx| handler(window, cx)), icon: None, icon_position: IconPosition::End, icon_size: IconSize::Small, icon_color: None, action, disabled: false, documentation_aside: None, })); self } pub fn toggleable_entry( mut self, label: impl Into<SharedString>, toggled: bool, position: IconPosition, action: Option<Box<dyn Action>>, handler: impl Fn(&mut Window, &mut App) + 'static, ) -> Self { self.items.push(ContextMenuItem::Entry(ContextMenuEntry { toggle: Some((position, toggled)), label: label.into(), handler: Rc::new(move |_, window, cx| handler(window, cx)), icon: None, icon_position: position, icon_size: IconSize::Small, icon_color: None, action, disabled: false, documentation_aside: None, })); self } pub fn custom_row( mut self, entry_render: impl Fn(&mut Window, &mut App) -> AnyElement + 'static, ) -> Self { self.items.push(ContextMenuItem::CustomEntry { entry_render: Box::new(entry_render), handler: Rc::new(|_, _, _| {}), selectable: false, }); self } pub fn custom_entry( mut self, entry_render: impl Fn(&mut Window, &mut App) -> AnyElement + 'static, handler: impl Fn(&mut Window, &mut App) + 'static, ) -> Self { self.items.push(ContextMenuItem::CustomEntry { entry_render: Box::new(entry_render), handler: Rc::new(move |_, window, cx| handler(window, cx)), selectable: true, }); self } pub fn label(mut self, label: impl Into<SharedString>) -> Self { self.items.push(ContextMenuItem::Label(label.into())); self } pub fn action(mut self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self { self.items.push(ContextMenuItem::Entry(ContextMenuEntry { toggle: None, label: label.into(), action: Some(action.boxed_clone()), handler: Rc::new(move |context, window, cx| { if let Some(context) = &context { window.focus(context); } window.dispatch_action(action.boxed_clone(), cx); }), icon: None, icon_position: IconPosition::End, icon_size: IconSize::Small, icon_color: None, disabled: false, documentation_aside: None, })); self } pub fn disabled_action( mut self, label: impl Into<SharedString>, action: Box<dyn Action>, ) -> Self { self.items.push(ContextMenuItem::Entry(ContextMenuEntry { toggle: None, label: label.into(), action: Some(action.boxed_clone()), handler: Rc::new(move |context, window, cx| { if let Some(context) = &context { window.focus(context); } window.dispatch_action(action.boxed_clone(), cx); }), icon: None, icon_size: IconSize::Small, icon_position: IconPosition::End, icon_color: None, disabled: true, documentation_aside: None, })); self } pub fn link(mut self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self { self.items.push(ContextMenuItem::Entry(ContextMenuEntry { toggle: None, label: label.into(), action: Some(action.boxed_clone()), handler: Rc::new(move |_, window, cx| window.dispatch_action(action.boxed_clone(), cx)), icon: Some(IconName::ArrowUpRight), icon_size: IconSize::XSmall, icon_position: IconPosition::End, icon_color: None, disabled: false, documentation_aside: None, })); self } pub fn keep_open_on_confirm(mut self) -> Self { self.keep_open_on_confirm = true; self } pub fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) { let context = self.action_context.as_ref(); if let Some( ContextMenuItem::Entry(ContextMenuEntry { handler, disabled: false, .. }) | ContextMenuItem::CustomEntry { handler, .. }, ) = self.selected_index.and_then(|ix| self.items.get(ix)) { (handler)(context, window, cx) } if !self.keep_open_on_confirm { cx.emit(DismissEvent); } } pub fn cancel(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) { cx.emit(DismissEvent); cx.emit(DismissEvent); } fn select_first(&mut self, _: &SelectFirst, _: &mut Window, cx: &mut Context<Self>) { if let Some(ix) = self.items.iter().position(|item| item.is_selectable()) { self.select_index(ix); } cx.notify(); } pub fn select_last(&mut self) -> Option<usize> { for (ix, item) in self.items.iter().enumerate().rev() { if item.is_selectable() { return self.select_index(ix); } } None } fn handle_select_last(&mut self, _: &SelectLast, _: &mut Window, cx: &mut Context<Self>) { if self.select_last().is_some() { cx.notify(); } } fn select_next(&mut self, _: &SelectNext, window: &mut Window, cx: &mut Context<Self>) { if let Some(ix) = self.selected_index { let next_index = ix + 1; if self.items.len() <= next_index { self.select_first(&SelectFirst, window, cx); } else { for (ix, item) in self.items.iter().enumerate().skip(next_index) { if item.is_selectable() { self.select_index(ix); cx.notify(); break; } } } } else { self.select_first(&SelectFirst, window, cx); } } pub fn select_previous( &mut self, _: &SelectPrevious, window: &mut Window, cx: &mut Context<Self>, ) { if let Some(ix) = self.selected_index { if ix == 0 { self.handle_select_last(&SelectLast, window, cx); } else { for (ix, item) in self.items.iter().enumerate().take(ix).rev() { if item.is_selectable() { self.select_index(ix); cx.notify(); break; } } } } else { self.handle_select_last(&SelectLast, window, cx); } } fn select_index(&mut self, ix: usize) -> Option<usize> { self.documentation_aside = None; let item = self.items.get(ix)?; if item.is_selectable() { self.selected_index = Some(ix); if let ContextMenuItem::Entry(entry) = item { if let Some(callback) = &entry.documentation_aside { self.documentation_aside = Some((ix, callback.clone())); } } } Some(ix) } pub fn on_action_dispatch( &mut self, dispatched: &dyn Action, window: &mut Window, cx: &mut Context<Self>, ) { if self.clicked { cx.propagate(); return; } if let Some(ix) = self.items.iter().position(|item| { if let ContextMenuItem::Entry(ContextMenuEntry { action: Some(action), disabled: false, .. }) = item { action.partial_eq(dispatched) } else { false } }) { self.select_index(ix); self.delayed = true; cx.notify(); let action = dispatched.boxed_clone(); cx.spawn_in(window, |this, mut cx| async move { cx.background_executor() .timer(Duration::from_millis(50)) .await; cx.update(|window, cx| { this.update(cx, |this, cx| { this.cancel(&menu::Cancel, window, cx); window.dispatch_action(action, cx); }) }) }) .detach_and_log_err(cx); } else { cx.propagate() } } pub fn on_blur_subscription(mut self, new_subscription: Subscription) -> Self { self._on_blur_subscription = new_subscription; self } } impl ContextMenuItem {