1use gpui::{AnyView, ClickEvent};
2
3use crate::{ButtonLike, ButtonLikeRounding, ElevationIndex, prelude::*};
4
5/// The position of a [`ToggleButton`] within a group of buttons.
6#[derive(Debug, PartialEq, Eq, Clone, Copy)]
7pub enum ToggleButtonPosition {
8 /// The toggle button is first in the group.
9 First,
10
11 /// The toggle button is in the middle of the group (i.e., it is not the first or last toggle button).
12 Middle,
13
14 /// The toggle button is last in the group.
15 Last,
16}
17
18#[derive(IntoElement, RegisterComponent)]
19pub struct ToggleButton {
20 base: ButtonLike,
21 position_in_group: Option<ToggleButtonPosition>,
22 label: SharedString,
23 label_color: Option<Color>,
24}
25
26impl ToggleButton {
27 pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
28 Self {
29 base: ButtonLike::new(id),
30 position_in_group: None,
31 label: label.into(),
32 label_color: None,
33 }
34 }
35
36 pub fn color(mut self, label_color: impl Into<Option<Color>>) -> Self {
37 self.label_color = label_color.into();
38 self
39 }
40
41 pub fn position_in_group(mut self, position: ToggleButtonPosition) -> Self {
42 self.position_in_group = Some(position);
43 self
44 }
45
46 pub fn first(self) -> Self {
47 self.position_in_group(ToggleButtonPosition::First)
48 }
49
50 pub fn middle(self) -> Self {
51 self.position_in_group(ToggleButtonPosition::Middle)
52 }
53
54 pub fn last(self) -> Self {
55 self.position_in_group(ToggleButtonPosition::Last)
56 }
57}
58
59impl Toggleable for ToggleButton {
60 fn toggle_state(mut self, selected: bool) -> Self {
61 self.base = self.base.toggle_state(selected);
62 self
63 }
64}
65
66impl SelectableButton for ToggleButton {
67 fn selected_style(mut self, style: ButtonStyle) -> Self {
68 self.base.selected_style = Some(style);
69 self
70 }
71}
72
73impl FixedWidth for ToggleButton {
74 fn width(mut self, width: DefiniteLength) -> Self {
75 self.base.width = Some(width);
76 self
77 }
78
79 fn full_width(mut self) -> Self {
80 self.base.width = Some(relative(1.));
81 self
82 }
83}
84
85impl Disableable for ToggleButton {
86 fn disabled(mut self, disabled: bool) -> Self {
87 self.base = self.base.disabled(disabled);
88 self
89 }
90}
91
92impl Clickable for ToggleButton {
93 fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self {
94 self.base = self.base.on_click(handler);
95 self
96 }
97
98 fn cursor_style(mut self, cursor_style: gpui::CursorStyle) -> Self {
99 self.base = self.base.cursor_style(cursor_style);
100 self
101 }
102}
103
104impl ButtonCommon for ToggleButton {
105 fn id(&self) -> &ElementId {
106 self.base.id()
107 }
108
109 fn style(mut self, style: ButtonStyle) -> Self {
110 self.base = self.base.style(style);
111 self
112 }
113
114 fn size(mut self, size: ButtonSize) -> Self {
115 self.base = self.base.size(size);
116 self
117 }
118
119 fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
120 self.base = self.base.tooltip(tooltip);
121 self
122 }
123
124 fn layer(mut self, elevation: ElevationIndex) -> Self {
125 self.base = self.base.layer(elevation);
126 self
127 }
128}
129
130impl RenderOnce for ToggleButton {
131 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
132 let is_disabled = self.base.disabled;
133 let is_selected = self.base.selected;
134
135 let label_color = if is_disabled {
136 Color::Disabled
137 } else if is_selected {
138 Color::Selected
139 } else {
140 self.label_color.unwrap_or_default()
141 };
142
143 self.base
144 .when_some(self.position_in_group, |this, position| match position {
145 ToggleButtonPosition::First => this.rounding(ButtonLikeRounding::Left),
146 ToggleButtonPosition::Middle => this.rounding(None),
147 ToggleButtonPosition::Last => this.rounding(ButtonLikeRounding::Right),
148 })
149 .child(
150 Label::new(self.label)
151 .color(label_color)
152 .line_height_style(LineHeightStyle::UiLabel),
153 )
154 }
155}
156
157impl Component for ToggleButton {
158 fn scope() -> ComponentScope {
159 ComponentScope::Input
160 }
161
162 fn sort_name() -> &'static str {
163 "ButtonC"
164 }
165
166 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
167 Some(
168 v_flex()
169 .gap_6()
170 .children(vec![
171 example_group_with_title(
172 "Button Styles",
173 vec![
174 single_example(
175 "Off",
176 ToggleButton::new("off", "Off")
177 .layer(ElevationIndex::Background)
178 .style(ButtonStyle::Filled)
179 .into_any_element(),
180 ),
181 single_example(
182 "On",
183 ToggleButton::new("on", "On")
184 .layer(ElevationIndex::Background)
185 .toggle_state(true)
186 .style(ButtonStyle::Filled)
187 .into_any_element(),
188 ),
189 single_example(
190 "Off – Disabled",
191 ToggleButton::new("disabled_off", "Disabled Off")
192 .layer(ElevationIndex::Background)
193 .disabled(true)
194 .style(ButtonStyle::Filled)
195 .into_any_element(),
196 ),
197 single_example(
198 "On – Disabled",
199 ToggleButton::new("disabled_on", "Disabled On")
200 .layer(ElevationIndex::Background)
201 .disabled(true)
202 .toggle_state(true)
203 .style(ButtonStyle::Filled)
204 .into_any_element(),
205 ),
206 ],
207 ),
208 example_group_with_title(
209 "Button Group",
210 vec![
211 single_example(
212 "Three Buttons",
213 h_flex()
214 .child(
215 ToggleButton::new("three_btn_first", "First")
216 .layer(ElevationIndex::Background)
217 .style(ButtonStyle::Filled)
218 .first()
219 .into_any_element(),
220 )
221 .child(
222 ToggleButton::new("three_btn_middle", "Middle")
223 .layer(ElevationIndex::Background)
224 .style(ButtonStyle::Filled)
225 .middle()
226 .toggle_state(true)
227 .into_any_element(),
228 )
229 .child(
230 ToggleButton::new("three_btn_last", "Last")
231 .layer(ElevationIndex::Background)
232 .style(ButtonStyle::Filled)
233 .last()
234 .into_any_element(),
235 )
236 .into_any_element(),
237 ),
238 single_example(
239 "Two Buttons",
240 h_flex()
241 .child(
242 ToggleButton::new("two_btn_first", "First")
243 .layer(ElevationIndex::Background)
244 .style(ButtonStyle::Filled)
245 .first()
246 .into_any_element(),
247 )
248 .child(
249 ToggleButton::new("two_btn_last", "Last")
250 .layer(ElevationIndex::Background)
251 .style(ButtonStyle::Filled)
252 .last()
253 .into_any_element(),
254 )
255 .into_any_element(),
256 ),
257 ],
258 ),
259 example_group_with_title(
260 "Alternate Sizes",
261 vec![
262 single_example(
263 "None",
264 ToggleButton::new("none", "None")
265 .layer(ElevationIndex::Background)
266 .style(ButtonStyle::Filled)
267 .size(ButtonSize::None)
268 .into_any_element(),
269 ),
270 single_example(
271 "Compact",
272 ToggleButton::new("compact", "Compact")
273 .layer(ElevationIndex::Background)
274 .style(ButtonStyle::Filled)
275 .size(ButtonSize::Compact)
276 .into_any_element(),
277 ),
278 single_example(
279 "Large",
280 ToggleButton::new("large", "Large")
281 .layer(ElevationIndex::Background)
282 .style(ButtonStyle::Filled)
283 .size(ButtonSize::Large)
284 .into_any_element(),
285 ),
286 ],
287 ),
288 ])
289 .into_any_element(),
290 )
291 }
292}