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    IconName, 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    // The button props
23    compact: Option<bool>,
24    outline: Option<bool>,
25    variant: Option<ButtonVariant>,
26    size: Option<Size>,
27    rounded: ButtonRounded,
28    anchor: Corner,
29}
30
31impl DropdownButton {
32    /// Create a new DropdownButton.
33    pub fn new(id: impl Into<ElementId>) -> Self {
34        Self {
35            id: id.into(),
36            style: StyleRefinement::default(),
37            button: None,
38            menu: None,
39            selected: false,
40            compact: None,
41            outline: None,
42            variant: None,
43            size: None,
44            rounded: ButtonRounded::default(),
45            anchor: Corner::TopRight,
46        }
47    }
48
49    /// Set the left button of the dropdown button.
50    pub fn button(mut self, button: Button) -> Self {
51        self.button = Some(button);
52        self
53    }
54
55    /// Set the dropdown menu of the button.
56    pub fn dropdown_menu(
57        mut self,
58        menu: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
59    ) -> Self {
60        self.menu = Some(Box::new(menu));
61        self
62    }
63
64    /// Set the dropdown menu of the button with anchor corner.
65    pub fn dropdown_menu_with_anchor(
66        mut self,
67        anchor: impl Into<Corner>,
68        menu: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
69    ) -> Self {
70        self.menu = Some(Box::new(menu));
71        self.anchor = anchor.into();
72        self
73    }
74
75    /// Set the rounded style of the button.
76    pub fn rounded(mut self, rounded: impl Into<ButtonRounded>) -> Self {
77        self.rounded = rounded.into();
78        self
79    }
80
81    /// Set the button to compact style.
82    ///
83    /// See also: [`Button::compact`]
84    pub fn compact(mut self) -> Self {
85        self.compact = Some(true);
86        self
87    }
88
89    /// Set the button to outline style.
90    ///
91    /// See also: [`Button::outline`]
92    pub fn outline(mut self) -> Self {
93        self.outline = Some(true);
94        self
95    }
96}
97
98impl Styled for DropdownButton {
99    fn style(&mut self) -> &mut gpui::StyleRefinement {
100        &mut self.style
101    }
102}
103
104impl Sizable for DropdownButton {
105    fn with_size(mut self, size: impl Into<Size>) -> Self {
106        self.size = Some(size.into());
107        self
108    }
109}
110
111impl ButtonVariants for DropdownButton {
112    fn with_variant(mut self, variant: ButtonVariant) -> Self {
113        self.variant = Some(variant);
114        self
115    }
116}
117
118impl Selectable for DropdownButton {
119    fn selected(mut self, selected: bool) -> Self {
120        self.selected = selected;
121        self
122    }
123
124    fn is_selected(&self) -> bool {
125        self.selected
126    }
127}
128
129impl RenderOnce for DropdownButton {
130    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
131        let rounded = self
132            .variant
133            .map(|variant| variant.is_ghost() && !self.selected)
134            .unwrap_or(false);
135
136        div()
137            .id(self.id)
138            .h_flex()
139            .refine_style(&self.style)
140            .when_some(self.button, |this, button| {
141                this.child(
142                    button
143                        .rounded(self.rounded)
144                        .border_corners(Corners {
145                            top_left: true,
146                            top_right: rounded,
147                            bottom_left: true,
148                            bottom_right: rounded,
149                        })
150                        .border_edges(Edges {
151                            left: true,
152                            top: true,
153                            right: true,
154                            bottom: true,
155                        })
156                        .selected(self.selected)
157                        .when_some(self.compact, |this, _| this.compact())
158                        .when_some(self.outline, |this, _| this.outline())
159                        .when_some(self.size, |this, size| this.with_size(size))
160                        .when_some(self.variant, |this, variant| this.with_variant(variant)),
161                )
162                .when_some(self.menu, |this, menu| {
163                    this.child(
164                        Button::new("popup")
165                            .icon(IconName::ChevronDown)
166                            .rounded(self.rounded)
167                            .border_edges(Edges {
168                                left: rounded,
169                                top: true,
170                                right: true,
171                                bottom: true,
172                            })
173                            .border_corners(Corners {
174                                top_left: rounded,
175                                top_right: true,
176                                bottom_left: rounded,
177                                bottom_right: true,
178                            })
179                            .selected(self.selected)
180                            .when_some(self.compact, |this, _| this.compact())
181                            .when_some(self.outline, |this, _| this.outline())
182                            .when_some(self.size, |this, size| this.with_size(size))
183                            .when_some(self.variant, |this, variant| this.with_variant(variant))
184                            .dropdown_menu_with_anchor(self.anchor, menu),
185                    )
186                })
187            })
188    }
189}