gpui_component/button/
dropdown_button.rs

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    // The button props
24    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    /// Create a new DropdownButton.
35    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    /// Set the left button of the dropdown button.
54    pub fn button(mut self, button: Button) -> Self {
55        self.button = Some(button);
56        self
57    }
58
59    /// Set the dropdown menu of the button.
60    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    /// Set the dropdown menu of the button with anchor corner.
69    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    /// Set the rounded style of the button.
80    pub fn rounded(mut self, rounded: impl Into<ButtonRounded>) -> Self {
81        self.rounded = rounded.into();
82        self
83    }
84
85    /// Set the button to compact style.
86    ///
87    /// See also: [`Button::compact`]
88    pub fn compact(mut self) -> Self {
89        self.compact = true;
90        self
91    }
92
93    /// Set the button to outline style.
94    ///
95    /// See also: [`Button::outline`]
96    pub fn outline(mut self) -> Self {
97        self.outline = true;
98        self
99    }
100
101    /// Set the button to loading state.
102    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}