1use gpui::{AnyView, DefiniteLength, Hsla};
2
3use super::button_like::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle};
4use crate::{ElevationIndex, Indicator, SelectableButton, TintColor, prelude::*};
5use crate::{IconName, IconSize};
6
7use super::button_icon::ButtonIcon;
8
9/// The shape of an [`IconButton`].
10#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
11pub enum IconButtonShape {
12 Square,
13 Wide,
14}
15
16#[derive(IntoElement, RegisterComponent)]
17pub struct IconButton {
18 base: ButtonLike,
19 shape: IconButtonShape,
20 icon: IconName,
21 icon_size: IconSize,
22 icon_color: Color,
23 selected_icon: Option<IconName>,
24 selected_icon_color: Option<Color>,
25 indicator: Option<Indicator>,
26 indicator_border_color: Option<Hsla>,
27 alpha: Option<f32>,
28}
29
30impl IconButton {
31 pub fn new(id: impl Into<ElementId>, icon: IconName) -> Self {
32 let mut this = Self {
33 base: ButtonLike::new(id),
34 shape: IconButtonShape::Square,
35 icon,
36 icon_size: IconSize::Small,
37 icon_color: Color::Default,
38 selected_icon: None,
39 selected_icon_color: None,
40 indicator: None,
41 indicator_border_color: None,
42 alpha: None,
43 };
44 this.base.base = this.base.base.debug_selector(|| format!("ICON-{:?}", icon));
45 this
46 }
47
48 pub fn shape(mut self, shape: IconButtonShape) -> Self {
49 self.shape = shape;
50 self
51 }
52
53 pub fn icon_size(mut self, icon_size: IconSize) -> Self {
54 self.icon_size = icon_size;
55 self
56 }
57
58 pub fn icon_color(mut self, icon_color: Color) -> Self {
59 self.icon_color = icon_color;
60 self
61 }
62
63 pub fn alpha(mut self, alpha: f32) -> Self {
64 self.alpha = Some(alpha);
65 self
66 }
67
68 pub fn selected_icon(mut self, icon: impl Into<Option<IconName>>) -> Self {
69 self.selected_icon = icon.into();
70 self
71 }
72
73 pub fn on_right_click(
74 mut self,
75 handler: impl Fn(&gpui::ClickEvent, &mut Window, &mut App) + 'static,
76 ) -> Self {
77 self.base = self.base.on_right_click(handler);
78 self
79 }
80
81 /// Sets the icon color used when the button is in a selected state.
82 pub fn selected_icon_color(mut self, color: impl Into<Option<Color>>) -> Self {
83 self.selected_icon_color = color.into();
84 self
85 }
86
87 pub fn indicator(mut self, indicator: Indicator) -> Self {
88 self.indicator = Some(indicator);
89 self
90 }
91
92 pub fn indicator_border_color(mut self, color: Option<Hsla>) -> Self {
93 self.indicator_border_color = color;
94
95 self
96 }
97}
98
99impl Disableable for IconButton {
100 fn disabled(mut self, disabled: bool) -> Self {
101 self.base = self.base.disabled(disabled);
102 self
103 }
104}
105
106impl Toggleable for IconButton {
107 fn toggle_state(mut self, selected: bool) -> Self {
108 self.base = self.base.toggle_state(selected);
109 self
110 }
111}
112
113impl SelectableButton for IconButton {
114 fn selected_style(mut self, style: ButtonStyle) -> Self {
115 self.base = self.base.selected_style(style);
116 self
117 }
118}
119
120impl Clickable for IconButton {
121 fn on_click(
122 mut self,
123 handler: impl Fn(&gpui::ClickEvent, &mut Window, &mut App) + 'static,
124 ) -> Self {
125 self.base = self.base.on_click(handler);
126 self
127 }
128
129 fn cursor_style(mut self, cursor_style: gpui::CursorStyle) -> Self {
130 self.base = self.base.cursor_style(cursor_style);
131 self
132 }
133}
134
135impl FixedWidth for IconButton {
136 fn width(mut self, width: impl Into<DefiniteLength>) -> Self {
137 self.base = self.base.width(width);
138 self
139 }
140
141 fn full_width(mut self) -> Self {
142 self.base = self.base.full_width();
143 self
144 }
145}
146
147impl ButtonCommon for IconButton {
148 fn id(&self) -> &ElementId {
149 self.base.id()
150 }
151
152 fn style(mut self, style: ButtonStyle) -> Self {
153 self.base = self.base.style(style);
154 self
155 }
156
157 fn size(mut self, size: ButtonSize) -> Self {
158 self.base = self.base.size(size);
159 self
160 }
161
162 fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
163 self.base = self.base.tooltip(tooltip);
164 self
165 }
166
167 fn tab_index(mut self, tab_index: impl Into<isize>) -> Self {
168 self.base = self.base.tab_index(tab_index);
169 self
170 }
171
172 fn layer(mut self, elevation: ElevationIndex) -> Self {
173 self.base = self.base.layer(elevation);
174 self
175 }
176
177 fn track_focus(mut self, focus_handle: &gpui::FocusHandle) -> Self {
178 self.base = self.base.track_focus(focus_handle);
179 self
180 }
181}
182
183impl VisibleOnHover for IconButton {
184 fn visible_on_hover(mut self, group_name: impl Into<SharedString>) -> Self {
185 self.base = self.base.visible_on_hover(group_name);
186 self
187 }
188}
189
190impl RenderOnce for IconButton {
191 #[allow(refining_impl_trait)]
192 fn render(self, window: &mut Window, cx: &mut App) -> ButtonLike {
193 let is_disabled = self.base.disabled;
194 let is_selected = self.base.selected;
195 let selected_style = self.base.selected_style;
196
197 let color = self.icon_color.color(cx).opacity(self.alpha.unwrap_or(1.0));
198 self.base
199 .map(|this| match self.shape {
200 IconButtonShape::Square => {
201 let size = self.icon_size.square(window, cx);
202 this.width(size).height(size.into())
203 }
204 IconButtonShape::Wide => this,
205 })
206 .child(
207 ButtonIcon::new(self.icon)
208 .disabled(is_disabled)
209 .toggle_state(is_selected)
210 .selected_icon(self.selected_icon)
211 .selected_icon_color(self.selected_icon_color)
212 .when_some(selected_style, |this, style| this.selected_style(style))
213 .when_some(self.indicator, |this, indicator| {
214 this.indicator(indicator)
215 .indicator_border_color(self.indicator_border_color)
216 })
217 .size(self.icon_size)
218 .color(Color::Custom(color)),
219 )
220 }
221}
222
223impl Component for IconButton {
224 fn scope() -> ComponentScope {
225 ComponentScope::Input
226 }
227
228 fn sort_name() -> &'static str {
229 "ButtonB"
230 }
231
232 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
233 Some(
234 v_flex()
235 .gap_6()
236 .children(vec![
237 example_group_with_title(
238 "Icon Button Styles",
239 vec![
240 // TODO: Remove/organize this later
241 single_example(
242 "SIZES",
243 h_flex()
244 .gap_0p5()
245 .child(
246 IconButton::new("tinted", IconName::Debug)
247 .icon_size(IconSize::Indicator)
248 .layer(ElevationIndex::Background)
249 .style(ButtonStyle::Tinted(TintColor::Accent)),
250 )
251 .child(
252 IconButton::new("tinted", IconName::Debug)
253 .icon_size(IconSize::XSmall)
254 .layer(ElevationIndex::Background)
255 .style(ButtonStyle::Tinted(TintColor::Accent)),
256 )
257 .child(
258 IconButton::new("tinted", IconName::Debug)
259 .icon_size(IconSize::Small)
260 .layer(ElevationIndex::Background)
261 .style(ButtonStyle::Tinted(TintColor::Accent)),
262 )
263 .child(
264 IconButton::new("tinted", IconName::Debug)
265 .icon_size(IconSize::Medium)
266 .layer(ElevationIndex::Background)
267 .style(ButtonStyle::Tinted(TintColor::Accent)),
268 )
269 .child(
270 IconButton::new("tinted", IconName::Debug)
271 .icon_size(IconSize::XLarge)
272 .layer(ElevationIndex::Background)
273 .style(ButtonStyle::Tinted(TintColor::Accent)),
274 )
275 .into_any_element(),
276 ),
277 single_example(
278 "Default",
279 IconButton::new("default", IconName::Check)
280 .layer(ElevationIndex::Background)
281 .into_any_element(),
282 ),
283 single_example(
284 "Filled",
285 IconButton::new("filled", IconName::Check)
286 .layer(ElevationIndex::Background)
287 .style(ButtonStyle::Filled)
288 .into_any_element(),
289 ),
290 single_example(
291 "Subtle",
292 IconButton::new("subtle", IconName::Check)
293 .layer(ElevationIndex::Background)
294 .style(ButtonStyle::Subtle)
295 .into_any_element(),
296 ),
297 single_example(
298 "Tinted",
299 IconButton::new("tinted", IconName::Check)
300 .layer(ElevationIndex::Background)
301 .style(ButtonStyle::Tinted(TintColor::Accent))
302 .into_any_element(),
303 ),
304 single_example(
305 "Transparent",
306 IconButton::new("transparent", IconName::Check)
307 .layer(ElevationIndex::Background)
308 .style(ButtonStyle::Transparent)
309 .into_any_element(),
310 ),
311 ],
312 ),
313 example_group_with_title(
314 "Icon Button Shapes",
315 vec![
316 single_example(
317 "Square",
318 IconButton::new("square", IconName::Check)
319 .style(ButtonStyle::Filled)
320 .layer(ElevationIndex::Background)
321 .into_any_element(),
322 ),
323 single_example(
324 "Wide",
325 IconButton::new("wide", IconName::Check)
326 .shape(IconButtonShape::Wide)
327 .style(ButtonStyle::Filled)
328 .layer(ElevationIndex::Background)
329 .into_any_element(),
330 ),
331 ],
332 ),
333 example_group_with_title(
334 "Icon Button Sizes",
335 vec![
336 single_example(
337 "XSmall",
338 IconButton::new("xsmall", IconName::Check)
339 .icon_size(IconSize::XSmall)
340 .style(ButtonStyle::Filled)
341 .layer(ElevationIndex::Background)
342 .into_any_element(),
343 ),
344 single_example(
345 "Small",
346 IconButton::new("small", IconName::Check)
347 .icon_size(IconSize::Small)
348 .style(ButtonStyle::Filled)
349 .layer(ElevationIndex::Background)
350 .into_any_element(),
351 ),
352 single_example(
353 "Medium",
354 IconButton::new("medium", IconName::Check)
355 .icon_size(IconSize::Medium)
356 .style(ButtonStyle::Filled)
357 .layer(ElevationIndex::Background)
358 .into_any_element(),
359 ),
360 single_example(
361 "XLarge",
362 IconButton::new("xlarge", IconName::Check)
363 .icon_size(IconSize::XLarge)
364 .style(ButtonStyle::Filled)
365 .layer(ElevationIndex::Background)
366 .into_any_element(),
367 ),
368 ],
369 ),
370 example_group_with_title(
371 "Special States",
372 vec![
373 single_example(
374 "Disabled",
375 IconButton::new("disabled", IconName::Check)
376 .disabled(true)
377 .style(ButtonStyle::Filled)
378 .layer(ElevationIndex::Background)
379 .into_any_element(),
380 ),
381 single_example(
382 "Selected",
383 IconButton::new("selected", IconName::Check)
384 .toggle_state(true)
385 .style(ButtonStyle::Filled)
386 .layer(ElevationIndex::Background)
387 .into_any_element(),
388 ),
389 single_example(
390 "With Indicator",
391 IconButton::new("indicator", IconName::Check)
392 .indicator(Indicator::dot().color(Color::Success))
393 .style(ButtonStyle::Filled)
394 .layer(ElevationIndex::Background)
395 .into_any_element(),
396 ),
397 ],
398 ),
399 example_group_with_title(
400 "Custom Colors",
401 vec![
402 single_example(
403 "Custom Icon Color",
404 IconButton::new("custom_color", IconName::Check)
405 .icon_color(Color::Accent)
406 .style(ButtonStyle::Filled)
407 .layer(ElevationIndex::Background)
408 .into_any_element(),
409 ),
410 single_example(
411 "With Alpha",
412 IconButton::new("alpha", IconName::Check)
413 .alpha(0.5)
414 .style(ButtonStyle::Filled)
415 .layer(ElevationIndex::Background)
416 .into_any_element(),
417 ),
418 ],
419 ),
420 ])
421 .into_any_element(),
422 )
423 }
424}