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}