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 Element<ViewState = 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(
 89            &mut self,
 90            _view: &mut S,
 91            cx: &mut ViewContext<S>,
 92        ) -> impl Element<ViewState = S> {
 93            Story::container(cx)
 94                .child(Story::title_for::<_, ContextMenu<S>>(cx))
 95                .child(Story::label(cx, "Default"))
 96                .child(ContextMenu::new([
 97                    ContextMenuItem::header("Section header"),
 98                    ContextMenuItem::Separator,
 99                    ContextMenuItem::entry(Label::new("Some entry")),
100                ]))
101        }
102    }
103}