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::Wide,
35 icon,
36 icon_size: IconSize::default(),
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 single_example(
241 "Default",
242 IconButton::new("default", IconName::Check)
243 .layer(ElevationIndex::Background)
244 .into_any_element(),
245 ),
246 single_example(
247 "Filled",
248 IconButton::new("filled", IconName::Check)
249 .layer(ElevationIndex::Background)
250 .style(ButtonStyle::Filled)
251 .into_any_element(),
252 ),
253 single_example(
254 "Subtle",
255 IconButton::new("subtle", IconName::Check)
256 .layer(ElevationIndex::Background)
257 .style(ButtonStyle::Subtle)
258 .into_any_element(),
259 ),
260 single_example(
261 "Tinted",
262 IconButton::new("tinted", IconName::Check)
263 .layer(ElevationIndex::Background)
264 .style(ButtonStyle::Tinted(TintColor::Accent))
265 .into_any_element(),
266 ),
267 single_example(
268 "Transparent",
269 IconButton::new("transparent", IconName::Check)
270 .layer(ElevationIndex::Background)
271 .style(ButtonStyle::Transparent)
272 .into_any_element(),
273 ),
274 ],
275 ),
276 example_group_with_title(
277 "Icon Button Shapes",
278 vec![
279 single_example(
280 "Square",
281 IconButton::new("square", IconName::Check)
282 .shape(IconButtonShape::Square)
283 .style(ButtonStyle::Filled)
284 .layer(ElevationIndex::Background)
285 .into_any_element(),
286 ),
287 single_example(
288 "Wide",
289 IconButton::new("wide", IconName::Check)
290 .shape(IconButtonShape::Wide)
291 .style(ButtonStyle::Filled)
292 .layer(ElevationIndex::Background)
293 .into_any_element(),
294 ),
295 ],
296 ),
297 example_group_with_title(
298 "Icon Button Sizes",
299 vec![
300 single_example(
301 "XSmall",
302 IconButton::new("xsmall", IconName::Check)
303 .icon_size(IconSize::XSmall)
304 .style(ButtonStyle::Filled)
305 .layer(ElevationIndex::Background)
306 .into_any_element(),
307 ),
308 single_example(
309 "Small",
310 IconButton::new("small", IconName::Check)
311 .icon_size(IconSize::Small)
312 .style(ButtonStyle::Filled)
313 .layer(ElevationIndex::Background)
314 .into_any_element(),
315 ),
316 single_example(
317 "Medium",
318 IconButton::new("medium", IconName::Check)
319 .icon_size(IconSize::Medium)
320 .style(ButtonStyle::Filled)
321 .layer(ElevationIndex::Background)
322 .into_any_element(),
323 ),
324 single_example(
325 "XLarge",
326 IconButton::new("xlarge", IconName::Check)
327 .icon_size(IconSize::XLarge)
328 .style(ButtonStyle::Filled)
329 .layer(ElevationIndex::Background)
330 .into_any_element(),
331 ),
332 ],
333 ),
334 example_group_with_title(
335 "Special States",
336 vec![
337 single_example(
338 "Disabled",
339 IconButton::new("disabled", IconName::Check)
340 .disabled(true)
341 .style(ButtonStyle::Filled)
342 .layer(ElevationIndex::Background)
343 .into_any_element(),
344 ),
345 single_example(
346 "Selected",
347 IconButton::new("selected", IconName::Check)
348 .toggle_state(true)
349 .style(ButtonStyle::Filled)
350 .layer(ElevationIndex::Background)
351 .into_any_element(),
352 ),
353 single_example(
354 "With Indicator",
355 IconButton::new("indicator", IconName::Check)
356 .indicator(Indicator::dot().color(Color::Success))
357 .style(ButtonStyle::Filled)
358 .layer(ElevationIndex::Background)
359 .into_any_element(),
360 ),
361 ],
362 ),
363 example_group_with_title(
364 "Custom Colors",
365 vec![
366 single_example(
367 "Custom Icon Color",
368 IconButton::new("custom_color", IconName::Check)
369 .icon_color(Color::Accent)
370 .style(ButtonStyle::Filled)
371 .layer(ElevationIndex::Background)
372 .into_any_element(),
373 ),
374 single_example(
375 "With Alpha",
376 IconButton::new("alpha", IconName::Check)
377 .alpha(0.5)
378 .style(ButtonStyle::Filled)
379 .layer(ElevationIndex::Background)
380 .into_any_element(),
381 ),
382 ],
383 ),
384 ])
385 .into_any_element(),
386 )
387 }
388}