gpui_component/button/
button_group.rs

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