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