1392 lines · 48.6 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,        documentation_aside: Option<DocumentationAside>,    },} 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,        edge: DocumentationEdge,        render: impl Fn(&mut App) -> AnyElement + 'static,    ) -> Self {        self.documentation_aside = Some(DocumentationAside {            side,            edge,            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,    documentation_aside: Option<(usize, DocumentationAside)>,    fixed_width: Option<DefiniteLength>,} #[derive(Copy, Clone, PartialEq, Eq)]pub enum DocumentationSide {    Left,    Right,} #[derive(Copy, Default, Clone, PartialEq, Eq)]pub enum DocumentationEdge {    #[default]    Top,    Bottom,} #[derive(Clone)]pub struct DocumentationAside {    pub side: DocumentationSide,    pub edge: DocumentationEdge,    pub render: Rc<dyn Fn(&mut App) -> AnyElement>,} impl DocumentationAside {    pub fn new(        side: DocumentationSide,        edge: DocumentationEdge,        render: Rc<dyn Fn(&mut App) -> AnyElement>,    ) -> Self {        Self { side, edge, 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| 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,        )    }     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| 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 push_item(&mut self, item: impl Into<ContextMenuItem>) {        self.items.push(item.into());    }     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,            custom_icon_path: None,            custom_icon_svg: 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,            custom_icon_path: None,            custom_icon_svg: 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,            custom_icon_path: None,            custom_icon_svg: 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,            custom_icon_path: None,            custom_icon_svg: None,            icon_position: position,            icon_size: IconSize::Small,

Lines 1–500 of 1392.