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
178impl VisibleOnHover for IconButton {
179 fn visible_on_hover(mut self, group_name: impl Into<SharedString>) -> Self {
180 self.base = self.base.visible_on_hover(group_name);
181 self
182 }
183}
184
185impl RenderOnce for IconButton {
186 #[allow(refining_impl_trait)]
187 fn render(self, window: &mut Window, cx: &mut App) -> ButtonLike {
188 let is_disabled = self.base.disabled;
189 let is_selected = self.base.selected;
190 let selected_style = self.base.selected_style;
191
192 let color = self.icon_color.color(cx).opacity(self.alpha.unwrap_or(1.0));
193 self.base
194 .map(|this| match self.shape {
195 IconButtonShape::Square => {
196 let size = self.icon_size.square(window, cx);
197 this.width(size).height(size.into())
198 }
199 IconButtonShape::Wide => this,
200 })
201 .child(
202 ButtonIcon::new(self.icon)
203 .disabled(is_disabled)
204 .toggle_state(is_selected)
205 .selected_icon(self.selected_icon)
206 .selected_icon_color(self.selected_icon_color)
207 .when_some(selected_style, |this, style| this.selected_style(style))
208 .when_some(self.indicator, |this, indicator| {
209 this.indicator(indicator)
210 .indicator_border_color(self.indicator_border_color)
211 })
212 .size(self.icon_size)
213 .color(Color::Custom(color)),
214 )
215 }
216}
217
218impl Component for IconButton {
219 fn scope() -> ComponentScope {
220 ComponentScope::Input
221 }
222
223 fn sort_name() -> &'static str {
224 "ButtonB"
225 }
226
227 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
228 Some(
229 v_flex()
230 .gap_6()
231 .children(vec![
232 example_group_with_title(
233 "Icon Button Styles",
234 vec![
235 single_example(
236 "Default",
237 IconButton::new("default", IconName::Check)
238 .layer(ElevationIndex::Background)
239 .into_any_element(),
240 ),
241 single_example(
242 "Filled",
243 IconButton::new("filled", IconName::Check)
244 .layer(ElevationIndex::Background)
245 .style(ButtonStyle::Filled)
246 .into_any_element(),
247 ),
248 single_example(
249 "Subtle",
250 IconButton::new("subtle", IconName::Check)
251 .layer(ElevationIndex::Background)
252 .style(ButtonStyle::Subtle)
253 .into_any_element(),
254 ),
255 single_example(
256 "Tinted",
257 IconButton::new("tinted", IconName::Check)
258 .layer(ElevationIndex::Background)
259 .style(ButtonStyle::Tinted(TintColor::Accent))
260 .into_any_element(),
261 ),
262 single_example(
263 "Transparent",
264 IconButton::new("transparent", IconName::Check)
265 .layer(ElevationIndex::Background)
266 .style(ButtonStyle::Transparent)
267 .into_any_element(),
268 ),
269 ],
270 ),
271 example_group_with_title(
272 "Icon Button Shapes",
273 vec![
274 single_example(
275 "Square",
276 IconButton::new("square", IconName::Check)
277 .shape(IconButtonShape::Square)
278 .style(ButtonStyle::Filled)
279 .layer(ElevationIndex::Background)
280 .into_any_element(),
281 ),
282 single_example(
283 "Wide",
284 IconButton::new("wide", IconName::Check)
285 .shape(IconButtonShape::Wide)
286 .style(ButtonStyle::Filled)
287 .layer(ElevationIndex::Background)
288 .into_any_element(),
289 ),
290 ],
291 ),
292 example_group_with_title(
293 "Icon Button Sizes",
294 vec![
295 single_example(
296 "XSmall",
297 IconButton::new("xsmall", IconName::Check)
298 .icon_size(IconSize::XSmall)
299 .style(ButtonStyle::Filled)
300 .layer(ElevationIndex::Background)
301 .into_any_element(),
302 ),
303 single_example(
304 "Small",
305 IconButton::new("small", IconName::Check)
306 .icon_size(IconSize::Small)
307 .style(ButtonStyle::Filled)
308 .layer(ElevationIndex::Background)
309 .into_any_element(),
310 ),
311 single_example(
312 "Medium",
313 IconButton::new("medium", IconName::Check)
314 .icon_size(IconSize::Medium)
315 .style(ButtonStyle::Filled)
316 .layer(ElevationIndex::Background)
317 .into_any_element(),
318 ),
319 single_example(
320 "XLarge",
321 IconButton::new("xlarge", IconName::Check)
322 .icon_size(IconSize::XLarge)
323 .style(ButtonStyle::Filled)
324 .layer(ElevationIndex::Background)
325 .into_any_element(),
326 ),
327 ],
328 ),
329 example_group_with_title(
330 "Special States",
331 vec![
332 single_example(
333 "Disabled",
334 IconButton::new("disabled", IconName::Check)
335 .disabled(true)
336 .style(ButtonStyle::Filled)
337 .layer(ElevationIndex::Background)
338 .into_any_element(),
339 ),
340 single_example(
341 "Selected",
342 IconButton::new("selected", IconName::Check)
343 .toggle_state(true)
344 .style(ButtonStyle::Filled)
345 .layer(ElevationIndex::Background)
346 .into_any_element(),
347 ),
348 single_example(
349 "With Indicator",
350 IconButton::new("indicator", IconName::Check)
351 .indicator(Indicator::dot().color(Color::Success))
352 .style(ButtonStyle::Filled)
353 .layer(ElevationIndex::Background)
354 .into_any_element(),
355 ),
356 ],
357 ),
358 example_group_with_title(
359 "Custom Colors",
360 vec![
361 single_example(
362 "Custom Icon Color",
363 IconButton::new("custom_color", IconName::Check)
364 .icon_color(Color::Accent)
365 .style(ButtonStyle::Filled)
366 .layer(ElevationIndex::Background)
367 .into_any_element(),
368 ),
369 single_example(
370 "With Alpha",
371 IconButton::new("alpha", IconName::Check)
372 .alpha(0.5)
373 .style(ButtonStyle::Filled)
374 .layer(ElevationIndex::Background)
375 .into_any_element(),
376 ),
377 ],
378 ),
379 ])
380 .into_any_element(),
381 )
382 }
383}