context_menu.rs

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