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 {
75 self.compact = true;
76 self
77 }
78
79 pub fn outline(mut self) -> Self {
81 self.outline = true;
82 self
83 }
84
85 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 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 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 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}