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