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