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, IntoComponent)]
17#[component(scope = "Input")]
18pub struct IconButton {
19 base: ButtonLike,
20 shape: IconButtonShape,
21 icon: IconName,
22 icon_size: IconSize,
23 icon_color: Color,
24 selected_icon: Option<IconName>,
25 selected_icon_color: Option<Color>,
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 indicator: None,
42 indicator_border_color: None,
43 alpha: None,
44 };
45 this.base.base = this.base.base.debug_selector(|| format!("ICON-{:?}", icon));
46 this
47 }
48
49 pub fn shape(mut self, shape: IconButtonShape) -> Self {
50 self.shape = shape;
51 self
52 }
53
54 pub fn icon_size(mut self, icon_size: IconSize) -> Self {
55 self.icon_size = icon_size;
56 self
57 }
58
59 pub fn icon_color(mut self, icon_color: Color) -> Self {
60 self.icon_color = icon_color;
61 self
62 }
63
64 pub fn alpha(mut self, alpha: f32) -> Self {
65 self.alpha = Some(alpha);
66 self
67 }
68
69 pub fn selected_icon(mut self, icon: impl Into<Option<IconName>>) -> Self {
70 self.selected_icon = icon.into();
71 self
72 }
73
74 pub fn on_right_click(
75 mut self,
76 handler: impl Fn(&gpui::ClickEvent, &mut Window, &mut App) + 'static,
77 ) -> Self {
78 self.base = self.base.on_right_click(handler);
79 self
80 }
81
82 /// Sets the icon color used when the button is in a selected state.
83 pub fn selected_icon_color(mut self, color: impl Into<Option<Color>>) -> Self {
84 self.selected_icon_color = color.into();
85 self
86 }
87
88 pub fn indicator(mut self, indicator: Indicator) -> Self {
89 self.indicator = Some(indicator);
90 self
91 }
92
93 pub fn indicator_border_color(mut self, color: Option<Hsla>) -> Self {
94 self.indicator_border_color = color;
95
96 self
97 }
98}
99
100impl Disableable for IconButton {
101 fn disabled(mut self, disabled: bool) -> Self {
102 self.base = self.base.disabled(disabled);
103 self
104 }
105}
106
107impl Toggleable for IconButton {
108 fn toggle_state(mut self, selected: bool) -> Self {
109 self.base = self.base.toggle_state(selected);
110 self
111 }
112}
113
114impl SelectableButton for IconButton {
115 fn selected_style(mut self, style: ButtonStyle) -> Self {
116 self.base = self.base.selected_style(style);
117 self
118 }
119}
120
121impl Clickable for IconButton {
122 fn on_click(
123 mut self,
124 handler: impl Fn(&gpui::ClickEvent, &mut Window, &mut App) + 'static,
125 ) -> Self {
126 self.base = self.base.on_click(handler);
127 self
128 }
129
130 fn cursor_style(mut self, cursor_style: gpui::CursorStyle) -> Self {
131 self.base = self.base.cursor_style(cursor_style);
132 self
133 }
134}
135
136impl FixedWidth for IconButton {
137 fn width(mut self, width: DefiniteLength) -> Self {
138 self.base = self.base.width(width);
139 self
140 }
141
142 fn full_width(mut self) -> Self {
143 self.base = self.base.full_width();
144 self
145 }
146}
147
148impl ButtonCommon for IconButton {
149 fn id(&self) -> &ElementId {
150 self.base.id()
151 }
152
153 fn style(mut self, style: ButtonStyle) -> Self {
154 self.base = self.base.style(style);
155 self
156 }
157
158 fn size(mut self, size: ButtonSize) -> Self {
159 self.base = self.base.size(size);
160 self
161 }
162
163 fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
164 self.base = self.base.tooltip(tooltip);
165 self
166 }
167
168 fn layer(mut self, elevation: ElevationIndex) -> Self {
169 self.base = self.base.layer(elevation);
170 self
171 }
172}
173
174impl VisibleOnHover for IconButton {
175 fn visible_on_hover(mut self, group_name: impl Into<SharedString>) -> Self {
176 self.base = self.base.visible_on_hover(group_name);
177 self
178 }
179}
180
181impl RenderOnce for IconButton {
182 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
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 ComponentPreview for IconButton {
214 fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
215 v_flex()
216 .gap_6()
217 .children(vec![
218 example_group_with_title(
219 "Icon Button Styles",
220 vec![
221 single_example(
222 "Default",
223 IconButton::new("default", IconName::Check)
224 .layer(ElevationIndex::Background)
225 .into_any_element(),
226 ),
227 single_example(
228 "Filled",
229 IconButton::new("filled", IconName::Check)
230 .layer(ElevationIndex::Background)
231 .style(ButtonStyle::Filled)
232 .into_any_element(),
233 ),
234 single_example(
235 "Subtle",
236 IconButton::new("subtle", IconName::Check)
237 .layer(ElevationIndex::Background)
238 .style(ButtonStyle::Subtle)
239 .into_any_element(),
240 ),
241 single_example(
242 "Tinted",
243 IconButton::new("tinted", IconName::Check)
244 .layer(ElevationIndex::Background)
245 .style(ButtonStyle::Tinted(TintColor::Accent))
246 .into_any_element(),
247 ),
248 single_example(
249 "Transparent",
250 IconButton::new("transparent", IconName::Check)
251 .layer(ElevationIndex::Background)
252 .style(ButtonStyle::Transparent)
253 .into_any_element(),
254 ),
255 ],
256 ),
257 example_group_with_title(
258 "Icon Button Shapes",
259 vec![
260 single_example(
261 "Square",
262 IconButton::new("square", IconName::Check)
263 .shape(IconButtonShape::Square)
264 .style(ButtonStyle::Filled)
265 .layer(ElevationIndex::Background)
266 .into_any_element(),
267 ),
268 single_example(
269 "Wide",
270 IconButton::new("wide", IconName::Check)
271 .shape(IconButtonShape::Wide)
272 .style(ButtonStyle::Filled)
273 .layer(ElevationIndex::Background)
274 .into_any_element(),
275 ),
276 ],
277 ),
278 example_group_with_title(
279 "Icon Button Sizes",
280 vec![
281 single_example(
282 "Small",
283 IconButton::new("small", IconName::Check)
284 .icon_size(IconSize::XSmall)
285 .style(ButtonStyle::Filled)
286 .layer(ElevationIndex::Background)
287 .into_any_element(),
288 ),
289 single_example(
290 "Small",
291 IconButton::new("small", IconName::Check)
292 .icon_size(IconSize::Small)
293 .style(ButtonStyle::Filled)
294 .layer(ElevationIndex::Background)
295 .into_any_element(),
296 ),
297 single_example(
298 "Medium",
299 IconButton::new("medium", IconName::Check)
300 .icon_size(IconSize::Medium)
301 .style(ButtonStyle::Filled)
302 .layer(ElevationIndex::Background)
303 .into_any_element(),
304 ),
305 single_example(
306 "XLarge",
307 IconButton::new("xlarge", IconName::Check)
308 .icon_size(IconSize::XLarge)
309 .style(ButtonStyle::Filled)
310 .layer(ElevationIndex::Background)
311 .into_any_element(),
312 ),
313 ],
314 ),
315 example_group_with_title(
316 "Special States",
317 vec![
318 single_example(
319 "Disabled",
320 IconButton::new("disabled", IconName::Check)
321 .disabled(true)
322 .style(ButtonStyle::Filled)
323 .layer(ElevationIndex::Background)
324 .into_any_element(),
325 ),
326 single_example(
327 "Selected",
328 IconButton::new("selected", IconName::Check)
329 .toggle_state(true)
330 .style(ButtonStyle::Filled)
331 .layer(ElevationIndex::Background)
332 .into_any_element(),
333 ),
334 single_example(
335 "With Indicator",
336 IconButton::new("indicator", IconName::Check)
337 .indicator(Indicator::dot().color(Color::Success))
338 .style(ButtonStyle::Filled)
339 .layer(ElevationIndex::Background)
340 .into_any_element(),
341 ),
342 ],
343 ),
344 example_group_with_title(
345 "Custom Colors",
346 vec![
347 single_example(
348 "Custom Icon Color",
349 IconButton::new("custom_color", IconName::Check)
350 .icon_color(Color::Accent)
351 .style(ButtonStyle::Filled)
352 .layer(ElevationIndex::Background)
353 .into_any_element(),
354 ),
355 single_example(
356 "With Alpha",
357 IconButton::new("alpha", IconName::Check)
358 .alpha(0.5)
359 .style(ButtonStyle::Filled)
360 .layer(ElevationIndex::Background)
361 .into_any_element(),
362 ),
363 ],
364 ),
365 ])
366 .into_any_element()
367 }
368}