Skip to main content

kitchen_ui/widgets/
menu_select.rs

1//! The dropdown of fixed options.
2
3use std::rc::Rc;
4
5use gpui_kit::component::button::Button;
6use gpui_kit::component::menu::{DropdownMenu as _, PopupMenuItem};
7use gpui_kit::component::{Icon, IconName, Sizable as _, h_flex};
8use gpui_kit::{App, ElementId, IntoElement, ParentElement as _, RenderOnce, SharedString, Styled as _, Window, div, px};
9
10type OnPick = Rc<dyn Fn(usize, &mut Window, &mut App)>;
11
12/// Tallest the list of options gets before it scrolls (px).
13const MENU_MAX_H: f32 = 360.;
14
15/// A button showing the current option; pressing it lists the options, the current one checked.
16/// It fills its place, so a long option ends in "…" instead of being cut or pushing the panel
17/// wider.
18#[derive(IntoElement)]
19pub struct MenuSelect {
20    id: ElementId,
21    options: Vec<SharedString>,
22    selected: Option<usize>,
23    placeholder: SharedString,
24    on_pick: OnPick,
25}
26
27impl MenuSelect {
28    /// A dropdown of `options` with `selected` current; `on_pick` gets the index picked.
29    pub fn new(id: impl Into<ElementId>, options: Vec<SharedString>, selected: Option<usize>, on_pick: impl Fn(usize, &mut Window, &mut App) + 'static) -> Self {
30        Self { id: id.into(), options, selected, placeholder: "Choose…".into(), on_pick: Rc::new(on_pick) }
31    }
32
33    /// What the button shows while nothing is selected ("Choose…" by default).
34    pub fn placeholder(mut self, text: impl Into<SharedString>) -> Self {
35        self.placeholder = text.into();
36        self
37    }
38}
39
40impl RenderOnce for MenuSelect {
41    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
42        let Self { id, options, selected, placeholder, on_pick } = self;
43        let current = selected.and_then(|i| options.get(i).cloned()).unwrap_or(placeholder);
44        let face = h_flex().w_full().min_w_0().gap_1().child(div().flex_1().min_w_0().truncate().text_left().child(current)).child(Icon::new(IconName::ChevronDown).xsmall());
45        Button::new(id).outline().small().min_w_0().w_full().child(face).cursor_pointer().dropdown_menu(move |menu, _, _| {
46            let mut menu = menu.scrollable(true).max_h(px(MENU_MAX_H));
47            for (i, label) in options.iter().enumerate() {
48                let on_pick = on_pick.clone();
49                menu = menu.item(PopupMenuItem::new(label.clone()).checked(selected == Some(i)).on_click(move |_, window, cx| on_pick(i, window, cx)));
50            }
51            menu
52        })
53    }
54}