1use gpui::{
2 div, prelude::FluentBuilder, App, Context, Corner, Corners, Edges, ElementId,
3 InteractiveElement as _, IntoElement, ParentElement, RenderOnce, StyleRefinement, Styled,
4 Window,
5};
6
7use crate::{
8 menu::{DropdownMenu, PopupMenu},
9 Disableable, Selectable, Sizable, Size, StyledExt as _,
10};
11
12use super::{Button, ButtonRounded, ButtonVariant, ButtonVariants};
13
14#[derive(IntoElement)]
15pub struct DropdownButton {
16 id: ElementId,
17 style: StyleRefinement,
18 button: Option<Button>,
19 menu:
20 Option<Box<dyn Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static>>,
21 selected: bool,
22 disabled: bool,
23 compact: bool,
25 outline: bool,
26 loading: bool,
27 variant: ButtonVariant,
28 size: Size,
29 rounded: ButtonRounded,
30 anchor: Corner,
31}
32
33impl DropdownButton {
34 pub fn new(id: impl Into<ElementId>) -> Self {
36 Self {
37 id: id.into(),
38 style: StyleRefinement::default(),
39 button: None,
40 menu: None,
41 selected: false,
42 disabled: false,
43 compact: false,
44 outline: false,
45 loading: false,
46 variant: ButtonVariant::default(),
47 size: Size::default(),
48 rounded: ButtonRounded::default(),
49 anchor: Corner::TopRight,
50 }
51 }
52
53 pub fn button(mut self, button: Button) -> Self {
55 self.button = Some(button);
56 self
57 }
58
59 pub fn dropdown_menu(
61 mut self,
62 menu: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
63 ) -> Self {
64 self.menu = Some(Box::new(menu));
65 self
66 }
67
68 pub fn dropdown_menu_with_anchor(
70 mut self,
71 anchor: impl Into<Corner>,
72 menu: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
73 ) -> Self {
74 self.menu = Some(Box::new(menu));
75 self.anchor = anchor.into();
76 self
77 }
78
79 pub fn rounded(mut self, rounded: impl Into<ButtonRounded>) -> Self {
81 self.rounded = rounded.into();
82 self
83 }
84
85 pub fn compact(mut self) -> Self {
89 self.compact = true;
90 self
91 }
92
93 pub fn outline(mut self) -> Self {
97 self.outline = true;
98 self
99 }
100
101 pub fn loading(mut self, loading: bool) -> Self {
103 self.loading = loading;
104 self
105 }
106}
107
108impl Disableable for DropdownButton {
109 fn disabled(mut self, disabled: bool) -> Self {
110 self.disabled = disabled;
111 self
112 }
113}
114
115impl Styled for DropdownButton {
116 fn style(&mut self) -> &mut gpui::StyleRefinement {
117 &mut self.style
118 }
119}
120
121impl Sizable for DropdownButton {
122 fn with_size(mut self, size: impl Into<Size>) -> Self {
123 self.size = size.into();
124 self
125 }
126}
127
128impl ButtonVariants for DropdownButton {
129 fn with_variant(mut self, variant: ButtonVariant) -> Self {
130 self.variant = variant;
131 self
132 }
133}
134
135impl Selectable for DropdownButton {
136 fn selected(mut self, selected: bool) -> Self {
137 self.selected = selected;
138 self
139 }
140
141 fn is_selected(&self) -> bool {
142 self.selected
143 }
144}
145
146impl RenderOnce for DropdownButton {
147 fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
148 let rounded = self.variant.is_ghost() && !self.selected;
149
150 div()
151 .id(self.id)
152 .h_flex()
153 .refine_style(&self.style)
154 .when_some(self.button, |this, button| {
155 this.child(
156 button
157 .rounded(self.rounded)
158 .border_corners(Corners {
159 top_left: true,
160 top_right: rounded,
161 bottom_left: true,
162 bottom_right: rounded,
163 })
164 .border_edges(Edges {
165 left: true,
166 top: true,
167 right: true,
168 bottom: true,
169 })
170 .loading(self.loading)
171 .selected(self.selected)
172 .disabled(self.disabled || self.loading)
173 .when(self.compact, |this| this.compact())
174 .when(self.outline, |this| this.outline())
175 .with_size(self.size)
176 .with_variant(self.variant),
177 )
178 .when_some(self.menu, |this, menu| {
179 this.child(
180 Button::new("popup")
181 .dropdown_caret(true)
182 .rounded(self.rounded)
183 .border_edges(Edges {
184 left: rounded,
185 top: true,
186 right: true,
187 bottom: true,
188 })
189 .border_corners(Corners {
190 top_left: rounded,
191 top_right: true,
192 bottom_left: rounded,
193 bottom_right: true,
194 })
195 .selected(self.selected)
196 .disabled(self.disabled || self.loading)
197 .when(self.compact, |this| this.compact())
198 .when(self.outline, |this| this.outline())
199 .with_size(self.size)
200 .with_variant(self.variant)
201 .dropdown_menu_with_anchor(self.anchor, menu),
202 )
203 })
204 })
205 }
206}