dropdown_menu.rs

  1use gpui::{ClickEvent, Corner, CursorStyle, Entity, MouseButton};
  2
  3use crate::{ContextMenu, PopoverMenu, prelude::*};
  4
  5enum LabelKind {
  6    Text(SharedString),
  7    Element(AnyElement),
  8}
  9
 10#[derive(IntoElement)]
 11pub struct DropdownMenu {
 12    id: ElementId,
 13    label: LabelKind,
 14    menu: Entity<ContextMenu>,
 15    full_width: bool,
 16    disabled: bool,
 17}
 18
 19impl DropdownMenu {
 20    pub fn new(
 21        id: impl Into<ElementId>,
 22        label: impl Into<SharedString>,
 23        menu: Entity<ContextMenu>,
 24    ) -> Self {
 25        Self {
 26            id: id.into(),
 27            label: LabelKind::Text(label.into()),
 28            menu,
 29            full_width: false,
 30            disabled: false,
 31        }
 32    }
 33
 34    pub fn new_with_element(
 35        id: impl Into<ElementId>,
 36        label: AnyElement,
 37        menu: Entity<ContextMenu>,
 38    ) -> Self {
 39        Self {
 40            id: id.into(),
 41            label: LabelKind::Element(label),
 42            menu,
 43            full_width: false,
 44            disabled: false,
 45        }
 46    }
 47
 48    pub fn full_width(mut self, full_width: bool) -> Self {
 49        self.full_width = full_width;
 50        self
 51    }
 52}
 53
 54impl Disableable for DropdownMenu {
 55    fn disabled(mut self, disabled: bool) -> Self {
 56        self.disabled = disabled;
 57        self
 58    }
 59}
 60
 61impl RenderOnce for DropdownMenu {
 62    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
 63        PopoverMenu::new(self.id)
 64            .full_width(self.full_width)
 65            .menu(move |_window, _cx| Some(self.menu.clone()))
 66            .trigger(
 67                DropdownMenuTrigger::new(self.label)
 68                    .full_width(self.full_width)
 69                    .disabled(self.disabled),
 70            )
 71            .attach(Corner::BottomLeft)
 72    }
 73}
 74
 75#[derive(IntoElement)]
 76struct DropdownMenuTrigger {
 77    label: LabelKind,
 78    full_width: bool,
 79    selected: bool,
 80    disabled: bool,
 81    cursor_style: CursorStyle,
 82    on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
 83}
 84
 85impl DropdownMenuTrigger {
 86    pub fn new(label: LabelKind) -> Self {
 87        Self {
 88            label,
 89            full_width: false,
 90            selected: false,
 91            disabled: false,
 92            cursor_style: CursorStyle::default(),
 93            on_click: None,
 94        }
 95    }
 96
 97    pub fn full_width(mut self, full_width: bool) -> Self {
 98        self.full_width = full_width;
 99        self
100    }
101}
102
103impl Disableable for DropdownMenuTrigger {
104    fn disabled(mut self, disabled: bool) -> Self {
105        self.disabled = disabled;
106        self
107    }
108}
109
110impl Toggleable for DropdownMenuTrigger {
111    fn toggle_state(mut self, selected: bool) -> Self {
112        self.selected = selected;
113        self
114    }
115}
116
117impl Clickable for DropdownMenuTrigger {
118    fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self {
119        self.on_click = Some(Box::new(handler));
120        self
121    }
122
123    fn cursor_style(mut self, cursor_style: CursorStyle) -> Self {
124        self.cursor_style = cursor_style;
125        self
126    }
127}
128
129impl RenderOnce for DropdownMenuTrigger {
130    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
131        let disabled = self.disabled;
132
133        h_flex()
134            .id("dropdown-menu-trigger")
135            .justify_between()
136            .rounded_sm()
137            .bg(cx.theme().colors().editor_background)
138            .pl_2()
139            .pr_1p5()
140            .py_0p5()
141            .gap_2()
142            .min_w_20()
143            .map(|el| {
144                if self.full_width {
145                    el.w_full()
146                } else {
147                    el.flex_none().w_auto()
148                }
149            })
150            .map(|el| {
151                if disabled {
152                    el.cursor_not_allowed()
153                } else {
154                    el.cursor_pointer()
155                }
156            })
157            .child(match self.label {
158                LabelKind::Text(text) => Label::new(text)
159                    .color(if disabled {
160                        Color::Disabled
161                    } else {
162                        Color::Default
163                    })
164                    .into_any_element(),
165                LabelKind::Element(element) => element,
166            })
167            .child(
168                Icon::new(IconName::ChevronUpDown)
169                    .size(IconSize::XSmall)
170                    .color(if disabled {
171                        Color::Disabled
172                    } else {
173                        Color::Muted
174                    }),
175            )
176            .when_some(self.on_click.filter(|_| !disabled), |el, on_click| {
177                el.on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
178                    .on_click(move |event, window, cx| {
179                        cx.stop_propagation();
180                        (on_click)(event, window, cx)
181                    })
182            })
183    }
184}