783 lines · 32.9 KB · Rust
RawDownload
#![allow(missing_docs)]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, SelectPrev};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: Option<Box<dyn Action>>) -> Self {        self.action = 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_prev(&mut self, _: &SelectPrev, 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 {    fn is_selectable(&self) -> bool {        match self {            ContextMenuItem::Header(_)            | ContextMenuItem::Separator

Lines 1–500 of 783.