1use gpui::{ClickEvent, Corner, CursorStyle, Entity, MouseButton};
2
3use crate::{prelude::*, ContextMenu, PopoverMenu};
4
5#[derive(IntoElement)]
6pub struct DropdownMenu {
7 id: ElementId,
8 label: SharedString,
9 menu: Entity<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: Entity<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, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
44 PopoverMenu::new(self.id)
45 .full_width(self.full_width)
46 .menu(move |_window, _cx| Some(self.menu.clone()))
47 .trigger(DropdownMenuTrigger::new(self.label).full_width(self.full_width))
48 .attach(Corner::BottomLeft)
49 }
50}
51
52#[derive(IntoElement)]
53struct DropdownMenuTrigger {
54 label: SharedString,
55 full_width: bool,
56 selected: bool,
57 disabled: bool,
58 cursor_style: CursorStyle,
59 on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
60}
61
62impl DropdownMenuTrigger {
63 pub fn new(label: impl Into<SharedString>) -> Self {
64 Self {
65 label: label.into(),
66 full_width: false,
67 selected: false,
68 disabled: false,
69 cursor_style: CursorStyle::default(),
70 on_click: None,
71 }
72 }
73
74 pub fn full_width(mut self, full_width: bool) -> Self {
75 self.full_width = full_width;
76 self
77 }
78}
79
80impl Disableable for DropdownMenuTrigger {
81 fn disabled(mut self, disabled: bool) -> Self {
82 self.disabled = disabled;
83 self
84 }
85}
86
87impl Toggleable for DropdownMenuTrigger {
88 fn toggle_state(mut self, selected: bool) -> Self {
89 self.selected = selected;
90 self
91 }
92}
93
94impl Clickable for DropdownMenuTrigger {
95 fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self {
96 self.on_click = Some(Box::new(handler));
97 self
98 }
99
100 fn cursor_style(mut self, cursor_style: CursorStyle) -> Self {
101 self.cursor_style = cursor_style;
102 self
103 }
104}
105
106impl RenderOnce for DropdownMenuTrigger {
107 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
108 let disabled = self.disabled;
109
110 h_flex()
111 .id("dropdown-menu-trigger")
112 .justify_between()
113 .rounded_sm()
114 .bg(cx.theme().colors().editor_background)
115 .pl_2()
116 .pr_1p5()
117 .py_0p5()
118 .gap_2()
119 .min_w_20()
120 .map(|el| {
121 if self.full_width {
122 el.w_full()
123 } else {
124 el.flex_none().w_auto()
125 }
126 })
127 .map(|el| {
128 if disabled {
129 el.cursor_not_allowed()
130 } else {
131 el.cursor_pointer()
132 }
133 })
134 .child(Label::new(self.label).color(if disabled {
135 Color::Disabled
136 } else {
137 Color::Default
138 }))
139 .child(
140 Icon::new(IconName::ChevronUpDown)
141 .size(IconSize::XSmall)
142 .color(if disabled {
143 Color::Disabled
144 } else {
145 Color::Muted
146 }),
147 )
148 .when_some(self.on_click.filter(|_| !disabled), |el, on_click| {
149 el.on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
150 .on_click(move |event, window, cx| {
151 cx.stop_propagation();
152 (on_click)(event, window, cx)
153 })
154 })
155 }
156}