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