context_menu.rs

  1use crate::{prelude::*, ListItemVariant};
  2use crate::{v_stack, Label, List, ListEntry, ListItem, ListSeparator, ListSubHeader};
  3
  4pub enum ContextMenuItem {
  5    Header(SharedString),
  6    Entry(Label, Box<dyn gpui::Action>),
  7    Separator,
  8}
  9
 10impl Clone for ContextMenuItem {
 11    fn clone(&self) -> Self {
 12        match self {
 13            ContextMenuItem::Header(name) => ContextMenuItem::Header(name.clone()),
 14            ContextMenuItem::Entry(label, action) => {
 15                ContextMenuItem::Entry(label.clone(), action.boxed_clone())
 16            }
 17            ContextMenuItem::Separator => ContextMenuItem::Separator,
 18        }
 19    }
 20}
 21impl ContextMenuItem {
 22    fn to_list_item<V: 'static>(self) -> ListItem {
 23        match self {
 24            ContextMenuItem::Header(label) => ListSubHeader::new(label).into(),
 25            ContextMenuItem::Entry(label, action) => ListEntry::new(label)
 26                .variant(ListItemVariant::Inset)
 27                .on_click(action)
 28                .into(),
 29            ContextMenuItem::Separator => ListSeparator::new().into(),
 30        }
 31    }
 32
 33    pub fn header(label: impl Into<SharedString>) -> Self {
 34        Self::Header(label.into())
 35    }
 36
 37    pub fn separator() -> Self {
 38        Self::Separator
 39    }
 40
 41    pub fn entry(label: Label, action: impl Action) -> Self {
 42        Self::Entry(label, Box::new(action))
 43    }
 44}
 45
 46#[derive(Component, Clone)]
 47pub struct ContextMenu {
 48    items: Vec<ContextMenuItem>,
 49}
 50
 51impl ContextMenu {
 52    pub fn new(items: impl IntoIterator<Item = ContextMenuItem>) -> Self {
 53        Self {
 54            items: items.into_iter().collect(),
 55        }
 56    }
 57    // todo!()
 58    // cx.add_action(ContextMenu::select_first);
 59    // cx.add_action(ContextMenu::select_last);
 60    // cx.add_action(ContextMenu::select_next);
 61    // cx.add_action(ContextMenu::select_prev);
 62    // cx.add_action(ContextMenu::confirm);
 63    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
 64        v_stack()
 65            .flex()
 66            .bg(cx.theme().colors().elevated_surface_background)
 67            .border()
 68            .border_color(cx.theme().colors().border)
 69            .child(List::new(
 70                self.items
 71                    .into_iter()
 72                    .map(ContextMenuItem::to_list_item::<V>)
 73                    .collect(),
 74            ))
 75            .on_mouse_down_out(|_, _, cx| cx.dispatch_action(Box::new(menu::Cancel)))
 76    }
 77}
 78
 79use gpui::Action;
 80#[cfg(feature = "stories")]
 81pub use stories::*;
 82
 83#[cfg(feature = "stories")]
 84mod stories {
 85    use super::*;
 86    use crate::story::Story;
 87    use gpui::{action, Div, Render};
 88
 89    pub struct ContextMenuStory;
 90
 91    impl Render for ContextMenuStory {
 92        type Element = Div<Self>;
 93
 94        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
 95            #[action]
 96            struct PrintCurrentDate {}
 97
 98            Story::container(cx)
 99                .child(Story::title_for::<_, ContextMenu>(cx))
100                .child(Story::label(cx, "Default"))
101                .child(ContextMenu::new([
102                    ContextMenuItem::header("Section header"),
103                    ContextMenuItem::Separator,
104                    ContextMenuItem::entry(Label::new("Print current time"), PrintCurrentDate {}),
105                ]))
106                .on_action(|_, _: &PrintCurrentDate, _| {
107                    if let Ok(unix_time) = std::time::UNIX_EPOCH.elapsed() {
108                        println!("Current Unix time is {:?}", unix_time.as_secs());
109                    }
110                })
111        }
112    }
113}