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