1151 lines · 40.8 KB · Rust
RawDownload
use crate::{    Icon, IconButtonShape, IconName, IconSize, KeyBinding, Label, List, ListItem, ListSeparator,    ListSubHeader, h_flex, prelude::*, utils::WithRemSize, v_flex,};use gpui::{    Action, AnyElement, App, AppContext as _, DismissEvent, Entity, EventEmitter, FocusHandle,    Focusable, IntoElement, Render, Subscription, px,};use menu::{SelectFirst, SelectLast, SelectNext, SelectPrevious};use settings::Settings;use std::{rc::Rc, time::Duration};use theme::ThemeSettings; use super::Tooltip; 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,    },} 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<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,            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 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 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,    documentation_aside: Option<(usize, DocumentationAside)>,    fixed_width: Option<DefiniteLength>,} #[derive(Copy, Clone, PartialEq, Eq)]pub enum DocumentationSide {    Left,    Right,} #[derive(Clone)]pub struct DocumentationAside {    side: DocumentationSide,    render: 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 {                    builder: None,                    items: Default::default(),                    focus_handle,                    action_context: None,                    selected_index: None,                    delayed: false,                    clicked: false,                    key_context: "menu".into(),                    _on_blur_subscription,                    keep_open_on_confirm: false,                    documentation_aside: None,                    fixed_width: None,                    end_slot_action: None,                },                window,                cx,            )        })    }     /// 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| 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,                    key_context: "menu".into(),                    _on_blur_subscription,                    keep_open_on_confirm: true,                    documentation_aside: None,                    fixed_width: None,                    end_slot_action: 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,                key_context: "menu".into(),                _on_blur_subscription: cx.on_blur(                    &focus_handle,                    window,                    |this: &mut ContextMenu, window, cx| this.cancel(&menu::Cancel, window, cx),                ),                keep_open_on_confirm: false,                documentation_aside: None,                fixed_width: None,                end_slot_action: 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    }     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,            end_slot_icon: None,            end_slot_title: None,            end_slot_handler: None,            show_end_slot_on_hover: false,        }));        self    }     pub fn entry_with_end_slot(        mut self,        label: impl Into<SharedString>,        action: Option<Box<dyn Action>>,        handler: impl Fn(&mut Window, &mut App) + 'static,        end_slot_icon: IconName,        end_slot_title: SharedString,        end_slot_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,            end_slot_icon: Some(end_slot_icon),            end_slot_title: Some(end_slot_title),            end_slot_handler: Some(Rc::new(move |_, window, cx| end_slot_handler(window, cx))),            show_end_slot_on_hover: false,        }));        self    }     pub fn entry_with_end_slot_on_hover(        mut self,        label: impl Into<SharedString>,        action: Option<Box<dyn Action>>,        handler: impl Fn(&mut Window, &mut App) + 'static,        end_slot_icon: IconName,        end_slot_title: SharedString,        end_slot_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,            end_slot_icon: Some(end_slot_icon),            end_slot_title: Some(end_slot_title),            end_slot_handler: Some(Rc::new(move |_, window, cx| end_slot_handler(window, cx))),            show_end_slot_on_hover: true,        }));        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,            end_slot_icon: None,            end_slot_title: None,            end_slot_handler: None,            show_end_slot_on_hover: false,        }));        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,            end_slot_icon: None,            end_slot_title: None,            end_slot_handler: None,

Lines 1–500 of 1151.