context_menu.rs

 1use crate::{prelude::*, ListItemVariant};
 2use crate::{v_stack, Label, List, ListEntry, ListItem, ListSeparator, ListSubHeader};
 3
 4pub enum ContextMenuItem<S: 'static + Send + Sync> {
 5    Header(SharedString),
 6    Entry(Label<S>),
 7    Separator,
 8}
 9
10impl<S: 'static + Send + Sync> ContextMenuItem<S> {
11    fn to_list_item(self) -> ListItem<S> {
12        match self {
13            ContextMenuItem::Header(label) => ListSubHeader::new(label).into(),
14            ContextMenuItem::Entry(label) => {
15                ListEntry::new(label).variant(ListItemVariant::Inset).into()
16            }
17            ContextMenuItem::Separator => ListSeparator::new().into(),
18        }
19    }
20
21    pub fn header(label: impl Into<SharedString>) -> Self {
22        Self::Header(label.into())
23    }
24
25    pub fn separator() -> Self {
26        Self::Separator
27    }
28
29    pub fn entry(label: Label<S>) -> Self {
30        Self::Entry(label)
31    }
32}
33
34#[derive(Element)]
35pub struct ContextMenu<S: 'static + Send + Sync> {
36    items: Vec<ContextMenuItem<S>>,
37}
38
39impl<S: 'static + Send + Sync> ContextMenu<S> {
40    pub fn new(items: impl IntoIterator<Item = ContextMenuItem<S>>) -> Self {
41        Self {
42            items: items.into_iter().collect(),
43        }
44    }
45    fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl IntoAnyElement<S> {
46        let theme = theme(cx);
47
48        v_stack()
49            .flex()
50            .bg(theme.elevated_surface)
51            .border()
52            .border_color(theme.border)
53            .child(
54                List::new(
55                    self.items
56                        .drain(..)
57                        .map(ContextMenuItem::to_list_item)
58                        .collect(),
59                )
60                .toggle(ToggleState::Toggled),
61            )
62    }
63}
64
65#[cfg(feature = "stories")]
66pub use stories::*;
67
68#[cfg(feature = "stories")]
69mod stories {
70    use std::marker::PhantomData;
71
72    use crate::story::Story;
73
74    use super::*;
75
76    #[derive(Element)]
77    pub struct ContextMenuStory<S: 'static + Send + Sync> {
78        state_type: PhantomData<S>,
79    }
80
81    impl<S: 'static + Send + Sync> ContextMenuStory<S> {
82        pub fn new() -> Self {
83            Self {
84                state_type: PhantomData,
85            }
86        }
87
88        fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl IntoAnyElement<S> {
89            Story::container(cx)
90                .child(Story::title_for::<_, ContextMenu<S>>(cx))
91                .child(Story::label(cx, "Default"))
92                .child(ContextMenu::new([
93                    ContextMenuItem::header("Section header"),
94                    ContextMenuItem::Separator,
95                    ContextMenuItem::entry(Label::new("Some entry")),
96                ]))
97        }
98    }
99}