use crate::{ IconButtonShape, KeyBinding, List, ListItem, ListSeparator, ListSubHeader, Tooltip, prelude::*, utils::WithRemSize,};use gpui::{ Action, AnyElement, App, Bounds, Corner, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, Size, Subscription, anchored, canvas, prelude::*, px,};use menu::{SelectChild, SelectFirst, SelectLast, SelectNext, SelectParent, SelectPrevious};use settings::Settings;use std::{ cell::{Cell, RefCell}, collections::HashMap, rc::Rc, time::{Duration, Instant},};use theme::ThemeSettings; #[derive(Copy, Clone, Debug, PartialEq, Eq)]enum SubmenuOpenTrigger { Pointer, Keyboard,} struct OpenSubmenu { item_index: usize, entity: Entity<ContextMenu>, trigger_bounds: Option<Bounds<Pixels>>, offset: Option<Pixels>, _dismiss_subscription: Subscription,} enum SubmenuState { Closed, Open(OpenSubmenu),} #[derive(Clone, Copy, PartialEq, Eq, Default)]enum HoverTarget { #[default] None, MainMenu, Submenu,} pub enum ContextMenuItem { Separator, Header(SharedString), /// title, link_label, link_url HeaderWithLink(SharedString, SharedString, SharedString), // This could be folded into header 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, documentation_aside: Option<DocumentationAside>, }, Submenu { label: SharedString, icon: Option<IconName>, icon_color: Option<Color>, builder: Rc<dyn Fn(ContextMenu, &mut Window, &mut Context<ContextMenu>) -> ContextMenu>, },} impl ContextMenuItem { pub fn custom_entry( entry_render: impl Fn(&mut Window, &mut App) -> AnyElement + 'static, handler: impl Fn(&mut Window, &mut App) + 'static, documentation_aside: Option<DocumentationAside>, ) -> Self { Self::CustomEntry { entry_render: Box::new(entry_render), handler: Rc::new(move |_, window, cx| handler(window, cx)), selectable: true, documentation_aside, } }} pub struct ContextMenuEntry { toggle: Option<(IconPosition, bool)>, label: SharedString, icon: Option<IconName>, custom_icon_path: Option<SharedString>, custom_icon_svg: Option<SharedString>, 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<DocumentationAside>, end_slot_icon: Option<IconName>, end_slot_title: Option<SharedString>, end_slot_handler: Option<Rc<dyn Fn(Option<&FocusHandle>, &mut Window, &mut App)>>, show_end_slot_on_hover: bool,} impl ContextMenuEntry { pub fn new(label: impl Into<SharedString>) -> Self { ContextMenuEntry { toggle: None, label: label.into(), icon: None, custom_icon_path: None, custom_icon_svg: None, icon_position: IconPosition::Start, icon_size: IconSize::Small, icon_color: None, handler: Rc::new(|_, _, _| {}), action: None, disabled: false, documentation_aside: None, end_slot_icon: None, end_slot_title: None, end_slot_handler: None, show_end_slot_on_hover: false, } } 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 custom_icon_path(mut self, path: impl Into<SharedString>) -> Self { self.custom_icon_path = Some(path.into()); self.custom_icon_svg = None; // Clear other icon sources if custom path is set self.icon = None; self } pub fn custom_icon_svg(mut self, svg: impl Into<SharedString>) -> Self { self.custom_icon_svg = Some(svg.into()); self.custom_icon_path = None; // Clear other icon sources if custom path is set self.icon = None; 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, side: DocumentationSide, render: impl Fn(&mut App) -> AnyElement + 'static, ) -> Self { self.documentation_aside = Some(DocumentationAside { side, render: Rc::new(render), }); self }} impl FluentBuilder for ContextMenuEntry {} impl From<ContextMenuEntry> for ContextMenuItem { fn from(entry: ContextMenuEntry) -> Self { ContextMenuItem::Entry(entry) }} pub struct ContextMenu { builder: Option<Rc<dyn Fn(Self, &mut Window, &mut Context<Self>) -> Self>>, items: Vec<ContextMenuItem>, focus_handle: FocusHandle, action_context: Option<FocusHandle>, selected_index: Option<usize>, delayed: bool, clicked: bool, end_slot_action: Option<Box<dyn Action>>, key_context: SharedString, _on_blur_subscription: Subscription, keep_open_on_confirm: bool, fixed_width: Option<DefiniteLength>, main_menu: Option<Entity<ContextMenu>>, main_menu_observed_bounds: Rc<Cell<Option<Bounds<Pixels>>>>, // Docs aide-related fields documentation_aside: Option<(usize, DocumentationAside)>, aside_trigger_bounds: Rc<RefCell<HashMap<usize, Bounds<Pixels>>>>, // Submenu-related fields submenu_state: SubmenuState, hover_target: HoverTarget, submenu_safety_threshold_x: Option<Pixels>, submenu_trigger_bounds: Rc<Cell<Option<Bounds<Pixels>>>>, submenu_trigger_mouse_down: bool, ignore_blur_until: Option<Instant>,} #[derive(Copy, Clone, PartialEq, Eq)]pub enum DocumentationSide { Left, Right,} #[derive(Clone)]pub struct DocumentationAside { pub side: DocumentationSide, pub render: Rc<dyn Fn(&mut App) -> AnyElement>,} impl DocumentationAside { pub fn new(side: DocumentationSide, render: Rc<dyn Fn(&mut App) -> AnyElement>) -> Self { Self { side, render } }} 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 new( window: &mut Window, cx: &mut Context<Self>, f: impl FnOnce(Self, &mut Window, &mut Context<Self>) -> Self, ) -> Self { let focus_handle = cx.focus_handle(); let _on_blur_subscription = cx.on_blur( &focus_handle, window, |this: &mut ContextMenu, window, cx| { if let Some(ignore_until) = this.ignore_blur_until { if Instant::now() < ignore_until { return; } else { this.ignore_blur_until = None; } } if this.main_menu.is_none() { if let SubmenuState::Open(open_submenu) = &this.submenu_state { let submenu_focus = open_submenu.entity.read(cx).focus_handle.clone(); if submenu_focus.contains_focused(window, cx) { return; } } } this.cancel(&menu::Cancel, window, cx) }, ); window.refresh(); f( Self { builder: None, items: Default::default(), focus_handle, action_context: None, selected_index: None, delayed: false, clicked: false, end_slot_action: None, key_context: "menu".into(), _on_blur_subscription, keep_open_on_confirm: false, fixed_width: None, main_menu: None, main_menu_observed_bounds: Rc::new(Cell::new(None)), documentation_aside: None, aside_trigger_bounds: Rc::new(RefCell::new(HashMap::default())), submenu_state: SubmenuState::Closed, hover_target: HoverTarget::MainMenu, submenu_safety_threshold_x: None, submenu_trigger_bounds: Rc::new(Cell::new(None)), submenu_trigger_mouse_down: false, ignore_blur_until: None, }, window, cx, ) } pub fn build( window: &mut Window, cx: &mut App, f: impl FnOnce(Self, &mut Window, &mut Context<Self>) -> Self, ) -> Entity<Self> { cx.new(|cx| Self::new(window, cx, f)) } /// Builds a [`ContextMenu`] that will stay open when making changes instead of closing after each confirmation. /// /// The main difference from [`ContextMenu::build`] is the type of the `builder`, as we need to be able to hold onto /// it to call it again. pub fn build_persistent( window: &mut Window, cx: &mut App, builder: impl Fn(Self, &mut Window, &mut Context<Self>) -> Self + 'static, ) -> Entity<Self> { cx.new(|cx| { let builder = Rc::new(builder); let focus_handle = cx.focus_handle(); let _on_blur_subscription = cx.on_blur( &focus_handle, window, |this: &mut ContextMenu, window, cx| { if let Some(ignore_until) = this.ignore_blur_until { if Instant::now() < ignore_until { return; } else { this.ignore_blur_until = None; } } if this.main_menu.is_none() { if let SubmenuState::Open(open_submenu) = &this.submenu_state { let submenu_focus = open_submenu.entity.read(cx).focus_handle.clone(); if submenu_focus.contains_focused(window, cx) { return; } } } this.cancel(&menu::Cancel, window, cx) }, ); window.refresh(); (builder.clone())( Self { builder: Some(builder), items: Default::default(), focus_handle, action_context: None, selected_index: None, delayed: false, clicked: false, end_slot_action: None, key_context: "menu".into(), _on_blur_subscription, keep_open_on_confirm: true, fixed_width: None, main_menu: None, main_menu_observed_bounds: Rc::new(Cell::new(None)), documentation_aside: None, aside_trigger_bounds: Rc::new(RefCell::new(HashMap::default())), submenu_state: SubmenuState::Closed, hover_target: HoverTarget::MainMenu, submenu_safety_threshold_x: None, submenu_trigger_bounds: Rc::new(Cell::new(None)), submenu_trigger_mouse_down: false, ignore_blur_until: None, }, window, cx, ) }) } /// Rebuilds the menu. /// /// This is used to refresh the menu entries when entries are toggled when the menu is configured with /// `keep_open_on_confirm = true`. /// /// This only works if the [`ContextMenu`] was constructed using [`ContextMenu::build_persistent`]. Otherwise it is /// a no-op. pub fn rebuild(&mut self, window: &mut Window, cx: &mut Context<Self>) { let Some(builder) = self.builder.clone() else { return; }; // The way we rebuild the menu is a bit of a hack. let focus_handle = cx.focus_handle(); let new_menu = (builder.clone())( Self { builder: Some(builder), items: Default::default(), focus_handle: focus_handle.clone(), action_context: None, selected_index: None, delayed: false, clicked: false, end_slot_action: None, key_context: "menu".into(), _on_blur_subscription: cx.on_blur( &focus_handle, window, |this: &mut ContextMenu, window, cx| { if let Some(ignore_until) = this.ignore_blur_until { if Instant::now() < ignore_until { return; } else { this.ignore_blur_until = None; } } if this.main_menu.is_none() { if let SubmenuState::Open(open_submenu) = &this.submenu_state { let submenu_focus = open_submenu.entity.read(cx).focus_handle.clone(); if submenu_focus.contains_focused(window, cx) { return; } } } this.cancel(&menu::Cancel, window, cx) }, ), keep_open_on_confirm: false, fixed_width: None, main_menu: None, main_menu_observed_bounds: Rc::new(Cell::new(None)), documentation_aside: None, aside_trigger_bounds: Rc::new(RefCell::new(HashMap::default())), submenu_state: SubmenuState::Closed, hover_target: HoverTarget::MainMenu, submenu_safety_threshold_x: None, submenu_trigger_bounds: Rc::new(Cell::new(None)), submenu_trigger_mouse_down: false, ignore_blur_until: None, }, window, cx, ); self.items = new_menu.items; cx.notify(); } 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 header_with_link( mut self, title: impl Into<SharedString>, link_label: impl Into<SharedString>, link_url: impl Into<SharedString>, ) -> Self { self.items.push(ContextMenuItem::HeaderWithLink( title.into(), link_label.into(), link_url.into(), )); self } pub fn separator(mut self) -> Self { self.items.push(ContextMenuItem::Separator); self }Lines 1–500 of 2249. Load more
use crate::{ IconButtonShape, KeyBinding, List, ListItem, ListSeparator, ListSubHeader, Tooltip, prelude::*, utils::WithRemSize, }; use gpui::{ Action, AnyElement, App, Bounds, Corner, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, Size, Subscription, anchored, canvas, prelude::*, px, }; use menu::{SelectChild, SelectFirst, SelectLast, SelectNext, SelectParent, SelectPrevious}; use settings::Settings; use std::{ cell::{Cell, RefCell}, collections::HashMap, rc::Rc, time::{Duration, Instant}, }; use theme::ThemeSettings; #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum SubmenuOpenTrigger { Pointer, Keyboard, } struct OpenSubmenu { item_index: usize, entity: Entity<ContextMenu>, trigger_bounds: Option<Bounds<Pixels>>, offset: Option<Pixels>, _dismiss_subscription: Subscription, } enum SubmenuState { Closed, Open(OpenSubmenu), } #[derive(Clone, Copy, PartialEq, Eq, Default)] enum HoverTarget { #[default] None, MainMenu, Submenu, } pub enum ContextMenuItem { Separator, Header(SharedString), /// title, link_label, link_url HeaderWithLink(SharedString, SharedString, SharedString), // This could be folded into header 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, documentation_aside: Option<DocumentationAside>, }, Submenu { label: SharedString, icon: Option<IconName>, icon_color: Option<Color>, builder: Rc<dyn Fn(ContextMenu, &mut Window, &mut Context<ContextMenu>) -> ContextMenu>, }, } impl ContextMenuItem { pub fn custom_entry( entry_render: impl Fn(&mut Window, &mut App) -> AnyElement + 'static, handler: impl Fn(&mut Window, &mut App) + 'static, documentation_aside: Option<DocumentationAside>, ) -> Self { Self::CustomEntry { entry_render: Box::new(entry_render), handler: Rc::new(move |_, window, cx| handler(window, cx)), selectable: true, documentation_aside, } } } pub struct ContextMenuEntry { toggle: Option<(IconPosition, bool)>, label: SharedString, icon: Option<IconName>, custom_icon_path: Option<SharedString>, custom_icon_svg: Option<SharedString>, 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<DocumentationAside>, end_slot_icon: Option<IconName>, end_slot_title: Option<SharedString>, end_slot_handler: Option<Rc<dyn Fn(Option<&FocusHandle>, &mut Window, &mut App)>>, show_end_slot_on_hover: bool, } impl ContextMenuEntry { pub fn new(label: impl Into<SharedString>) -> Self { ContextMenuEntry { toggle: None, label: label.into(), icon: None, custom_icon_path: None, custom_icon_svg: None, icon_position: IconPosition::Start, icon_size: IconSize::Small, icon_color: None, handler: Rc::new(|_, _, _| {}), action: None, disabled: false, documentation_aside: None, end_slot_icon: None, end_slot_title: None, end_slot_handler: None, show_end_slot_on_hover: false, } } 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 custom_icon_path(mut self, path: impl Into<SharedString>) -> Self { self.custom_icon_path = Some(path.into()); self.custom_icon_svg = None; // Clear other icon sources if custom path is set self.icon = None; self } pub fn custom_icon_svg(mut self, svg: impl Into<SharedString>) -> Self { self.custom_icon_svg = Some(svg.into()); self.custom_icon_path = None; // Clear other icon sources if custom path is set self.icon = None; 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, side: DocumentationSide, render: impl Fn(&mut App) -> AnyElement + 'static, ) -> Self { self.documentation_aside = Some(DocumentationAside { side, render: Rc::new(render), }); self } } impl FluentBuilder for ContextMenuEntry {} impl From<ContextMenuEntry> for ContextMenuItem { fn from(entry: ContextMenuEntry) -> Self { ContextMenuItem::Entry(entry) } } pub struct ContextMenu { builder: Option<Rc<dyn Fn(Self, &mut Window, &mut Context<Self>) -> Self>>, items: Vec<ContextMenuItem>, focus_handle: FocusHandle, action_context: Option<FocusHandle>, selected_index: Option<usize>, delayed: bool, clicked: bool, end_slot_action: Option<Box<dyn Action>>, key_context: SharedString, _on_blur_subscription: Subscription, keep_open_on_confirm: bool, fixed_width: Option<DefiniteLength>, main_menu: Option<Entity<ContextMenu>>, main_menu_observed_bounds: Rc<Cell<Option<Bounds<Pixels>>>>, // Docs aide-related fields documentation_aside: Option<(usize, DocumentationAside)>, aside_trigger_bounds: Rc<RefCell<HashMap<usize, Bounds<Pixels>>>>, // Submenu-related fields submenu_state: SubmenuState, hover_target: HoverTarget, submenu_safety_threshold_x: Option<Pixels>, submenu_trigger_bounds: Rc<Cell<Option<Bounds<Pixels>>>>, submenu_trigger_mouse_down: bool, ignore_blur_until: Option<Instant>, } #[derive(Copy, Clone, PartialEq, Eq)] pub enum DocumentationSide { Left, Right, } #[derive(Clone)] pub struct DocumentationAside { pub side: DocumentationSide, pub render: Rc<dyn Fn(&mut App) -> AnyElement>, } impl DocumentationAside { pub fn new(side: DocumentationSide, render: Rc<dyn Fn(&mut App) -> AnyElement>) -> Self { Self { side, render } } } 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 new( window: &mut Window, cx: &mut Context<Self>, f: impl FnOnce(Self, &mut Window, &mut Context<Self>) -> Self, ) -> Self { let focus_handle = cx.focus_handle(); let _on_blur_subscription = cx.on_blur( &focus_handle, window, |this: &mut ContextMenu, window, cx| { if let Some(ignore_until) = this.ignore_blur_until { if Instant::now() < ignore_until { return; } else { this.ignore_blur_until = None; } } if this.main_menu.is_none() { if let SubmenuState::Open(open_submenu) = &this.submenu_state { let submenu_focus = open_submenu.entity.read(cx).focus_handle.clone(); if submenu_focus.contains_focused(window, cx) { return; } } } this.cancel(&menu::Cancel, window, cx) }, ); window.refresh(); f( Self { builder: None, items: Default::default(), focus_handle, action_context: None, selected_index: None, delayed: false, clicked: false, end_slot_action: None, key_context: "menu".into(), _on_blur_subscription, keep_open_on_confirm: false, fixed_width: None, main_menu: None, main_menu_observed_bounds: Rc::new(Cell::new(None)), documentation_aside: None, aside_trigger_bounds: Rc::new(RefCell::new(HashMap::default())), submenu_state: SubmenuState::Closed, hover_target: HoverTarget::MainMenu, submenu_safety_threshold_x: None, submenu_trigger_bounds: Rc::new(Cell::new(None)), submenu_trigger_mouse_down: false, ignore_blur_until: None, }, window, cx, ) } pub fn build( window: &mut Window, cx: &mut App, f: impl FnOnce(Self, &mut Window, &mut Context<Self>) -> Self, ) -> Entity<Self> { cx.new(|cx| Self::new(window, cx, f)) } /// Builds a [`ContextMenu`] that will stay open when making changes instead of closing after each confirmation. /// /// The main difference from [`ContextMenu::build`] is the type of the `builder`, as we need to be able to hold onto /// it to call it again. pub fn build_persistent( window: &mut Window, cx: &mut App, builder: impl Fn(Self, &mut Window, &mut Context<Self>) -> Self + 'static, ) -> Entity<Self> { cx.new(|cx| { let builder = Rc::new(builder); let focus_handle = cx.focus_handle(); let _on_blur_subscription = cx.on_blur( &focus_handle, window, |this: &mut ContextMenu, window, cx| { if let Some(ignore_until) = this.ignore_blur_until { if Instant::now() < ignore_until { return; } else { this.ignore_blur_until = None; } } if this.main_menu.is_none() { if let SubmenuState::Open(open_submenu) = &this.submenu_state { let submenu_focus = open_submenu.entity.read(cx).focus_handle.clone(); if submenu_focus.contains_focused(window, cx) { return; } } } this.cancel(&menu::Cancel, window, cx) }, ); window.refresh(); (builder.clone())( Self { builder: Some(builder), items: Default::default(), focus_handle, action_context: None, selected_index: None, delayed: false, clicked: false, end_slot_action: None, key_context: "menu".into(), _on_blur_subscription, keep_open_on_confirm: true, fixed_width: None, main_menu: None, main_menu_observed_bounds: Rc::new(Cell::new(None)), documentation_aside: None, aside_trigger_bounds: Rc::new(RefCell::new(HashMap::default())), submenu_state: SubmenuState::Closed, hover_target: HoverTarget::MainMenu, submenu_safety_threshold_x: None, submenu_trigger_bounds: Rc::new(Cell::new(None)), submenu_trigger_mouse_down: false, ignore_blur_until: None, }, window, cx, ) }) } /// Rebuilds the menu. /// /// This is used to refresh the menu entries when entries are toggled when the menu is configured with /// `keep_open_on_confirm = true`. /// /// This only works if the [`ContextMenu`] was constructed using [`ContextMenu::build_persistent`]. Otherwise it is /// a no-op. pub fn rebuild(&mut self, window: &mut Window, cx: &mut Context<Self>) { let Some(builder) = self.builder.clone() else { return; }; // The way we rebuild the menu is a bit of a hack. let focus_handle = cx.focus_handle(); let new_menu = (builder.clone())( Self { builder: Some(builder), items: Default::default(), focus_handle: focus_handle.clone(), action_context: None, selected_index: None, delayed: false, clicked: false, end_slot_action: None, key_context: "menu".into(), _on_blur_subscription: cx.on_blur( &focus_handle, window, |this: &mut ContextMenu, window, cx| { if let Some(ignore_until) = this.ignore_blur_until { if Instant::now() < ignore_until { return; } else { this.ignore_blur_until = None; } } if this.main_menu.is_none() { if let SubmenuState::Open(open_submenu) = &this.submenu_state { let submenu_focus = open_submenu.entity.read(cx).focus_handle.clone(); if submenu_focus.contains_focused(window, cx) { return; } } } this.cancel(&menu::Cancel, window, cx) }, ), keep_open_on_confirm: false, fixed_width: None, main_menu: None, main_menu_observed_bounds: Rc::new(Cell::new(None)), documentation_aside: None, aside_trigger_bounds: Rc::new(RefCell::new(HashMap::default())), submenu_state: SubmenuState::Closed, hover_target: HoverTarget::MainMenu, submenu_safety_threshold_x: None, submenu_trigger_bounds: Rc::new(Cell::new(None)), submenu_trigger_mouse_down: false, ignore_blur_until: None, }, window, cx, ); self.items = new_menu.items; cx.notify(); } 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 header_with_link( mut self, title: impl Into<SharedString>, link_label: impl Into<SharedString>, link_url: impl Into<SharedString>, ) -> Self { self.items.push(ContextMenuItem::HeaderWithLink( title.into(), link_label.into(), link_url.into(), )); self } pub fn separator(mut self) -> Self { self.items.push(ContextMenuItem::Separator); self }