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}