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#[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 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 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 pub fn child(mut self, child: Button) -> Self {
57 self.children.push(child.disabled(self.disabled));
58 self
59 }
60
61 pub fn children(mut self, children: impl IntoIterator<Item = Button>) -> Self {
63 self.children.extend(children);
64 self
65 }
66
67 pub fn multiple(mut self, multiple: bool) -> Self {
69 self.multiple = multiple;
70 self
71 }
72
73 pub fn compact(mut self) -> Self {
77 self.compact = true;
78 self
79 }
80
81 pub fn outline(mut self) -> Self {
85 self.outline = true;
86 self
87 }
88
89 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 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 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 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}