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