gpui_component/button/
button_group.rs

1use gpui::{
2    div, prelude::FluentBuilder as _, App, Corners, Edges, ElementId, InteractiveElement,
3    IntoElement, ParentElement, RenderOnce, StatefulInteractiveElement as _, StyleRefinement,
4    Styled, Window,
5};
6use std::{cell::Cell, rc::Rc};
7
8use crate::{
9    button::{Button, ButtonVariant, ButtonVariants},
10    Disableable, Sizable, Size, StyledExt,
11};
12
13/// A ButtonGroup element, to wrap multiple buttons in a group.
14#[derive(IntoElement)]
15pub struct ButtonGroup {
16    id: ElementId,
17    style: StyleRefinement,
18    children: Vec<Button>,
19    pub(super) multiple: bool,
20    pub(super) disabled: bool,
21
22    // The button props
23    pub(super) compact: bool,
24    pub(super) outline: bool,
25    pub(super) variant: Option<ButtonVariant>,
26    pub(super) size: Option<Size>,
27
28    on_click: Option<Box<dyn Fn(&Vec<usize>, &mut Window, &mut App) + 'static>>,
29}
30
31impl Disableable for ButtonGroup {
32    fn disabled(mut self, disabled: bool) -> Self {
33        self.disabled = disabled;
34        self
35    }
36}
37
38impl ButtonGroup {
39    /// Creates a new ButtonGroup.
40    pub fn new(id: impl Into<ElementId>) -> Self {
41        Self {
42            id: id.into(),
43            style: StyleRefinement::default(),
44            children: Vec::new(),
45            variant: None,
46            size: None,
47            compact: false,
48            outline: false,
49            multiple: false,
50            disabled: false,
51            on_click: None,
52        }
53    }
54
55    /// Adds a button as a child to the ButtonGroup.
56    pub fn child(mut self, child: Button) -> Self {
57        self.children.push(child.disabled(self.disabled));
58        self
59    }
60
61    /// Adds multiple buttons as children to the ButtonGroup.
62    pub fn children(mut self, children: impl IntoIterator<Item = Button>) -> Self {
63        self.children.extend(children);
64        self
65    }
66
67    /// With the multiple selection mode, default is false (single selection).
68    pub fn multiple(mut self, multiple: bool) -> Self {
69        self.multiple = multiple;
70        self
71    }
72
73    /// With the compact mode for the ButtonGroup.
74    ///
75    /// See also: [`Button::compact()`]
76    pub fn compact(mut self) -> Self {
77        self.compact = true;
78        self
79    }
80
81    /// With the outline mode for the ButtonGroup.
82    ///
83    /// See also: [`Button::outline()`]
84    pub fn outline(mut self) -> Self {
85        self.outline = true;
86        self
87    }
88
89    /// Sets the on_click handler for the ButtonGroup.
90    ///
91    /// The handler first argument is a vector of the selected button indices.
92    ///
93    /// The `&Vec<usize>` is the indices of the clicked (selected in `multiple` mode) buttons.
94    /// For example: `[0, 2, 3]` is means the first, third and fourth buttons are clicked.
95    ///
96    /// ```ignore
97    /// ButtonGroup::new("size-button")
98    ///    .child(Button::new("large").label("Large").selected(self.size == Size::Large))
99    ///    .child(Button::new("medium").label("Medium").selected(self.size == Size::Medium))
100    ///    .child(Button::new("small").label("Small").selected(self.size == Size::Small))
101    ///    .on_click(cx.listener(|view, clicks: &Vec<usize>, _, cx| {
102    ///        if clicks.contains(&0) {
103    ///            view.size = Size::Large;
104    ///        } else if clicks.contains(&1) {
105    ///            view.size = Size::Medium;
106    ///        } else if clicks.contains(&2) {
107    ///            view.size = Size::Small;
108    ///        }
109    ///        cx.notify();
110    ///    }))
111    /// ```
112    pub fn on_click(
113        mut self,
114        handler: impl Fn(&Vec<usize>, &mut Window, &mut App) + 'static,
115    ) -> Self {
116        self.on_click = Some(Box::new(handler));
117        self
118    }
119}
120
121impl Sizable for ButtonGroup {
122    fn with_size(mut self, size: impl Into<Size>) -> Self {
123        self.size = Some(size.into());
124        self
125    }
126}
127
128impl Styled for ButtonGroup {
129    fn style(&mut self) -> &mut gpui::StyleRefinement {
130        &mut self.style
131    }
132}
133
134impl ButtonVariants for ButtonGroup {
135    fn with_variant(mut self, variant: ButtonVariant) -> Self {
136        self.variant = Some(variant);
137        self
138    }
139}
140
141impl RenderOnce for ButtonGroup {
142    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
143        let children_len = self.children.len();
144        let mut selected_ixs: Vec<usize> = Vec::new();
145        let state = Rc::new(Cell::new(None));
146
147        for (ix, child) in self.children.iter().enumerate() {
148            if child.selected {
149                selected_ixs.push(ix);
150            }
151        }
152
153        div()
154            .id(self.id)
155            .flex()
156            .items_center()
157            .refine_style(&self.style)
158            .children(
159                self.children
160                    .into_iter()
161                    .enumerate()
162                    .map(|(child_index, child)| {
163                        let state = Rc::clone(&state);
164                        let child = if children_len == 1 {
165                            child
166                        } else if child_index == 0 {
167                            // First
168                            child
169                                .border_corners(Corners {
170                                    top_left: true,
171                                    top_right: false,
172                                    bottom_left: true,
173                                    bottom_right: false,
174                                })
175                                .border_edges(Edges {
176                                    left: true,
177                                    top: true,
178                                    right: true,
179                                    bottom: true,
180                                })
181                        } else if child_index == children_len - 1 {
182                            // Last
183                            child
184                                .border_edges(Edges {
185                                    left: false,
186                                    top: true,
187                                    right: true,
188                                    bottom: true,
189                                })
190                                .border_corners(Corners {
191                                    top_left: false,
192                                    top_right: true,
193                                    bottom_left: false,
194                                    bottom_right: true,
195                                })
196                        } else {
197                            // Middle
198                            child
199                                .border_corners(Corners::all(false))
200                                .border_edges(Edges {
201                                    left: false,
202                                    top: true,
203                                    right: true,
204                                    bottom: true,
205                                })
206                        }
207                        .when_some(self.size, |this, size| this.with_size(size))
208                        .when_some(self.variant, |this, variant| this.with_variant(variant))
209                        .when(self.compact, |this| this.compact())
210                        .when(self.outline, |this| this.outline())
211                        .on_click(move |_, _, _| {
212                            state.set(Some(child_index));
213                        });
214
215                        child
216                    }),
217            )
218            .when_some(
219                self.on_click.filter(|_| !self.disabled),
220                move |this, on_click| {
221                    this.on_click(move |_, window, cx| {
222                        let mut selected_ixs = selected_ixs.clone();
223                        if let Some(ix) = state.get() {
224                            if self.multiple {
225                                if let Some(pos) = selected_ixs.iter().position(|&i| i == ix) {
226                                    selected_ixs.remove(pos);
227                                } else {
228                                    selected_ixs.push(ix);
229                                }
230                            } else {
231                                selected_ixs.clear();
232                                selected_ixs.push(ix);
233                            }
234                        }
235
236                        on_click(&selected_ixs, window, cx);
237                    })
238                },
239            )
240    }
241}