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.
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    pub fn compact(mut self) -> Self {
75        self.compact = true;
76        self
77    }
78
79    /// With the outline mode for the ButtonGroup.
80    pub fn outline(mut self) -> Self {
81        self.outline = true;
82        self
83    }
84
85    /// Sets the on_click handler for the ButtonGroup.
86    ///
87    /// The handler first argument is a vector of the selected button indices.
88    ///
89    /// The `&Vec<usize>` is the indices of the clicked (selected in `multiple` mode) buttons.
90    /// For example: `[0, 2, 3]` is means the first, third and fourth buttons are clicked.
91    ///
92    /// ```ignore
93    /// ButtonGroup::new("size-button")
94    ///    .child(Button::new("large").label("Large").selected(self.size == Size::Large))
95    ///    .child(Button::new("medium").label("Medium").selected(self.size == Size::Medium))
96    ///    .child(Button::new("small").label("Small").selected(self.size == Size::Small))
97    ///    .on_click(cx.listener(|view, clicks: &Vec<usize>, _, cx| {
98    ///        if clicks.contains(&0) {
99    ///            view.size = Size::Large;
100    ///        } else if clicks.contains(&1) {
101    ///            view.size = Size::Medium;
102    ///        } else if clicks.contains(&2) {
103    ///            view.size = Size::Small;
104    ///        }
105    ///        cx.notify();
106    ///    }))
107    /// ```
108    pub fn on_click(
109        mut self,
110        handler: impl Fn(&Vec<usize>, &mut Window, &mut App) + 'static,
111    ) -> Self {
112        self.on_click = Some(Box::new(handler));
113        self
114    }
115}
116
117impl Sizable for ButtonGroup {
118    fn with_size(mut self, size: impl Into<Size>) -> Self {
119        self.size = Some(size.into());
120        self
121    }
122}
123
124impl Styled for ButtonGroup {
125    fn style(&mut self) -> &mut gpui::StyleRefinement {
126        &mut self.style
127    }
128}
129
130impl ButtonVariants for ButtonGroup {
131    fn with_variant(mut self, variant: ButtonVariant) -> Self {
132        self.variant = Some(variant);
133        self
134    }
135}
136
137impl RenderOnce for ButtonGroup {
138    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
139        let children_len = self.children.len();
140        let mut selected_ixs: Vec<usize> = Vec::new();
141        let state = Rc::new(Cell::new(None));
142
143        for (ix, child) in self.children.iter().enumerate() {
144            if child.selected {
145                selected_ixs.push(ix);
146            }
147        }
148
149        div()
150            .id(self.id)
151            .flex()
152            .items_center()
153            .refine_style(&self.style)
154            .children(
155                self.children
156                    .into_iter()
157                    .enumerate()
158                    .map(|(child_index, child)| {
159                        let state = Rc::clone(&state);
160                        let child = if children_len == 1 {
161                            child
162                        } else if child_index == 0 {
163                            // First
164                            child
165                                .border_corners(Corners {
166                                    top_left: true,
167                                    top_right: false,
168                                    bottom_left: true,
169                                    bottom_right: false,
170                                })
171                                .border_edges(Edges {
172                                    left: true,
173                                    top: true,
174                                    right: true,
175                                    bottom: true,
176                                })
177                        } else if child_index == children_len - 1 {
178                            // Last
179                            child
180                                .border_edges(Edges {
181                                    left: false,
182                                    top: true,
183                                    right: true,
184                                    bottom: true,
185                                })
186                                .border_corners(Corners {
187                                    top_left: false,
188                                    top_right: true,
189                                    bottom_left: false,
190                                    bottom_right: true,
191                                })
192                        } else {
193                            // Middle
194                            child
195                                .border_corners(Corners::all(false))
196                                .border_edges(Edges {
197                                    left: false,
198                                    top: true,
199                                    right: true,
200                                    bottom: true,
201                                })
202                        }
203                        .stop_propagation(false)
204                        .when_some(self.size, |this, size| this.with_size(size))
205                        .when_some(self.variant, |this, variant| this.with_variant(variant))
206                        .when(self.compact, |this| this.compact())
207                        .when(self.outline, |this| this.outline())
208                        .on_click(move |_, _, _| {
209                            state.set(Some(child_index));
210                        });
211
212                        child
213                    }),
214            )
215            .when_some(
216                self.on_click.filter(|_| !self.disabled),
217                move |this, on_click| {
218                    this.on_click(move |_, window, cx| {
219                        let mut selected_ixs = selected_ixs.clone();
220                        if let Some(ix) = state.get() {
221                            if self.multiple {
222                                if let Some(pos) = selected_ixs.iter().position(|&i| i == ix) {
223                                    selected_ixs.remove(pos);
224                                } else {
225                                    selected_ixs.push(ix);
226                                }
227                            } else {
228                                selected_ixs.clear();
229                                selected_ixs.push(ix);
230                            }
231                        }
232
233                        on_click(&selected_ixs, window, cx);
234                    })
235                },
236            )
237    }
238}