dropdown.rs

  1use std::rc::Rc;
  2
  3use gpui::{App, ElementId, IntoElement, RenderOnce};
  4use heck::ToTitleCase as _;
  5use ui::{
  6    ButtonSize, ContextMenu, DropdownMenu, DropdownStyle, FluentBuilder as _, IconPosition, px,
  7};
  8
  9#[derive(IntoElement)]
 10pub struct EnumVariantDropdown<T>
 11where
 12    T: strum::VariantArray + strum::VariantNames + Copy + PartialEq + Send + Sync + 'static,
 13{
 14    id: ElementId,
 15    current_value: T,
 16    variants: &'static [T],
 17    labels: &'static [&'static str],
 18    should_do_title_case: bool,
 19    tab_index: Option<isize>,
 20    on_change: Rc<dyn Fn(T, &mut App) + 'static>,
 21}
 22
 23impl<T> EnumVariantDropdown<T>
 24where
 25    T: strum::VariantArray + strum::VariantNames + Copy + PartialEq + Send + Sync + 'static,
 26{
 27    pub fn new(
 28        id: impl Into<ElementId>,
 29        current_value: T,
 30        variants: &'static [T],
 31        labels: &'static [&'static str],
 32        on_change: impl Fn(T, &mut App) + 'static,
 33    ) -> Self {
 34        Self {
 35            id: id.into(),
 36            current_value,
 37            variants,
 38            labels,
 39            should_do_title_case: true,
 40            tab_index: None,
 41            on_change: Rc::new(on_change),
 42        }
 43    }
 44
 45    pub fn title_case(mut self, title_case: bool) -> Self {
 46        self.should_do_title_case = title_case;
 47        self
 48    }
 49
 50    pub fn tab_index(mut self, tab_index: isize) -> Self {
 51        self.tab_index = Some(tab_index);
 52        self
 53    }
 54}
 55
 56impl<T> RenderOnce for EnumVariantDropdown<T>
 57where
 58    T: strum::VariantArray + strum::VariantNames + Copy + PartialEq + Send + Sync + 'static,
 59{
 60    fn render(self, window: &mut ui::Window, cx: &mut ui::App) -> impl gpui::IntoElement {
 61        let current_value_label = self.labels[self
 62            .variants
 63            .iter()
 64            .position(|v| *v == self.current_value)
 65            .unwrap()];
 66
 67        let context_menu = window.use_keyed_state(current_value_label, cx, |window, cx| {
 68            ContextMenu::new(window, cx, move |mut menu, _, _| {
 69                for (&value, &label) in std::iter::zip(self.variants, self.labels) {
 70                    let on_change = self.on_change.clone();
 71                    let current_value = self.current_value;
 72                    menu = menu.toggleable_entry(
 73                        if self.should_do_title_case {
 74                            label.to_title_case()
 75                        } else {
 76                            label.to_string()
 77                        },
 78                        value == current_value,
 79                        IconPosition::End,
 80                        None,
 81                        move |_, cx| {
 82                            on_change(value, cx);
 83                        },
 84                    );
 85                }
 86                menu
 87            })
 88        });
 89
 90        DropdownMenu::new(
 91            self.id,
 92            if self.should_do_title_case {
 93                current_value_label.to_title_case()
 94            } else {
 95                current_value_label.to_string()
 96            },
 97            context_menu,
 98        )
 99        .when_some(self.tab_index, |elem, tab_index| elem.tab_index(tab_index))
100        .trigger_size(ButtonSize::Medium)
101        .style(DropdownStyle::Outlined)
102        .offset(gpui::Point {
103            x: px(0.0),
104            y: px(2.0),
105        })
106        .into_any_element()
107    }
108}