ui/components/
toggle_group.rs1use gpui::ElementId;
2use std::rc::Rc;
3
4use crate::prelude::*;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
8pub enum ToggleGroupMode {
9 #[default]
11 Single,
12 Multiple,
14}
15
16#[derive(Clone)]
18pub struct ToggleGroupItem {
19 label: SharedString,
20 icon: Option<IconName>,
21}
22
23impl ToggleGroupItem {
24 pub fn new(label: impl Into<SharedString>) -> Self {
25 Self {
26 label: label.into(),
27 icon: None,
28 }
29 }
30
31 pub fn icon(mut self, icon: IconName) -> Self {
32 self.icon = Some(icon);
33 self
34 }
35}
36
37#[derive(IntoElement, RegisterComponent)]
43pub struct ToggleGroup {
44 id: ElementId,
45 items: Vec<ToggleGroupItem>,
46 mode: ToggleGroupMode,
47 selected: Vec<usize>,
48 disabled: bool,
49 on_change: Option<Rc<dyn Fn(Vec<usize>, &mut Window, &mut App) + 'static>>,
50}
51
52impl ToggleGroup {
53 pub fn new(id: impl Into<ElementId>, items: impl IntoIterator<Item = ToggleGroupItem>) -> Self {
54 Self {
55 id: id.into(),
56 items: items.into_iter().collect(),
57 mode: ToggleGroupMode::default(),
58 selected: Vec::new(),
59 disabled: false,
60 on_change: None,
61 }
62 }
63
64 pub fn mode(mut self, mode: ToggleGroupMode) -> Self {
65 self.mode = mode;
66 self
67 }
68
69 pub fn selected(mut self, indices: impl IntoIterator<Item = usize>) -> Self {
71 self.selected = indices.into_iter().collect();
72 self
73 }
74
75 pub fn disabled(mut self, disabled: bool) -> Self {
76 self.disabled = disabled;
77 self
78 }
79
80 pub fn on_change(
87 mut self,
88 handler: impl Fn(Vec<usize>, &mut Window, &mut App) + 'static,
89 ) -> Self {
90 self.on_change = Some(Rc::new(handler));
91 self
92 }
93}
94
95impl RenderOnce for ToggleGroup {
96 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
97 let selected = self.selected;
98 let disabled = self.disabled;
99 let mode = self.mode;
100 let group_id = self.id.clone();
101 let on_change = self.on_change;
102
103 let mut row = h_flex()
104 .id(self.id)
105 .gap_1()
106 .p(px(2.))
107 .rounded_md()
108 .bg(semantic::muted_bg(cx));
109
110 for (i, item) in self.items.into_iter().enumerate() {
111 let is_selected = selected.contains(&i);
112 let on_change = on_change.clone();
113 let current_selected = selected.clone();
114
115 let mut cell = h_flex()
116 .id((group_id.clone(), i.to_string()))
117 .flex_1()
118 .justify_center()
119 .items_center()
120 .gap_1()
121 .px_3()
122 .py_1p5()
123 .rounded_sm()
124 .when(is_selected, |this| {
125 this.bg(semantic::accent_bg(cx))
126 .text_color(semantic::accent_fg(cx))
127 })
128 .when(!is_selected && !disabled, |this| {
129 this.hover(|style| style.bg(semantic::hover_bg(cx)))
130 })
131 .when(disabled, |this| this.opacity(0.5))
132 .children(item.icon.map(|icon| {
133 Icon::new(icon)
134 .size(IconSize::Small)
135 .color(if is_selected {
136 Color::Accent
137 } else {
138 Color::Muted
139 })
140 .into_any_element()
141 }))
142 .child(
143 Label::new(item.label)
144 .size(LabelSize::Small)
145 .color(if is_selected {
146 Color::Accent
147 } else {
148 Color::Default
149 }),
150 );
151
152 if !disabled {
153 cell = cell.cursor_pointer().on_click(move |_, window, cx| {
154 if let Some(handler) = &on_change {
155 let next = match mode {
156 ToggleGroupMode::Single => vec![i],
157 ToggleGroupMode::Multiple => {
158 let mut next = current_selected.clone();
159 if let Some(pos) = next.iter().position(|&x| x == i) {
160 next.remove(pos);
161 } else {
162 next.push(i);
163 }
164 next
165 }
166 };
167 handler(next, window, cx);
168 }
169 });
170 }
171
172 row = row.child(cell);
173 }
174
175 row
176 }
177}
178
179impl Component for ToggleGroup {
180 fn scope() -> ComponentScope {
181 ComponentScope::Input
182 }
183
184 fn description() -> Option<&'static str> {
185 Some("A row of toggle buttons supporting single or multiple selection.")
186 }
187
188 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
189 Some(
190 v_flex()
191 .gap_4()
192 .child(
193 ToggleGroup::new(
194 "toggle-group-single",
195 [
196 ToggleGroupItem::new("List"),
197 ToggleGroupItem::new("Grid"),
198 ToggleGroupItem::new("Board"),
199 ],
200 )
201 .selected([1])
202 .into_any_element(),
203 )
204 .child(
205 ToggleGroup::new(
206 "toggle-group-multiple",
207 [
208 ToggleGroupItem::new("Bold"),
209 ToggleGroupItem::new("Italic"),
210 ToggleGroupItem::new("Underline"),
211 ],
212 )
213 .mode(ToggleGroupMode::Multiple)
214 .selected([0, 2])
215 .into_any_element(),
216 )
217 .child(
218 ToggleGroup::new(
219 "toggle-group-icons",
220 [
221 ToggleGroupItem::new("Left").icon(IconName::ArrowLeft),
222 ToggleGroupItem::new("Center").icon(IconName::Dash),
223 ToggleGroupItem::new("Right").icon(IconName::ArrowRight),
224 ],
225 )
226 .selected([0])
227 .disabled(true)
228 .into_any_element(),
229 )
230 .into_any_element(),
231 )
232 }
233}