1use gpui::{AnyView, DefiniteLength, Hsla};
2
3use super::button_like::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle};
4use crate::{
5 ElevationIndex, Icon, IconWithIndicator, Indicator, SelectableButton, TintColor, prelude::*,
6};
7use crate::{IconName, IconSize};
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 selected_style: Option<ButtonStyle>,
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 selected_style: None,
42 indicator: None,
43 indicator_border_color: None,
44 alpha: None,
45 };
46 this.base.base = this.base.base.debug_selector(|| format!("ICON-{:?}", icon));
47 this
48 }
49
50 pub fn shape(mut self, shape: IconButtonShape) -> Self {
51 self.shape = shape;
52 self
53 }
54
55 pub fn icon_size(mut self, icon_size: IconSize) -> Self {
56 self.icon_size = icon_size;
57 self
58 }
59
60 pub fn icon_color(mut self, icon_color: Color) -> Self {
61 self.icon_color = icon_color;
62 self
63 }
64
65 pub fn alpha(mut self, alpha: f32) -> Self {
66 self.alpha = Some(alpha);
67 self
68 }
69
70 pub fn selected_icon(mut self, icon: impl Into<Option<IconName>>) -> Self {
71 self.selected_icon = icon.into();
72 self
73 }
74
75 pub fn on_right_click(
76 mut self,
77 handler: impl Fn(&gpui::ClickEvent, &mut Window, &mut App) + 'static,
78 ) -> Self {
79 self.base = self.base.on_right_click(handler);
80 self
81 }
82
83 /// Sets the icon color used when the button is in a selected state.
84 pub fn selected_icon_color(mut self, color: impl Into<Option<Color>>) -> Self {
85 self.selected_icon_color = color.into();
86 self
87 }
88
89 pub fn indicator(mut self, indicator: Indicator) -> Self {
90 self.indicator = Some(indicator);
91 self
92 }
93
94 pub fn indicator_border_color(mut self, color: Option<Hsla>) -> Self {
95 self.indicator_border_color = color;
96
97 self
98 }
99}
100
101impl Disableable for IconButton {
102 fn disabled(mut self, disabled: bool) -> Self {
103 self.base = self.base.disabled(disabled);
104 self
105 }
106}
107
108impl Toggleable for IconButton {
109 fn toggle_state(mut self, selected: bool) -> Self {
110 self.base = self.base.toggle_state(selected);
111 self
112 }
113}
114
115impl SelectableButton for IconButton {
116 fn selected_style(mut self, style: ButtonStyle) -> Self {
117 self.selected_style = Some(style);
118 self.base = self.base.selected_style(style);
119 self
120 }
121}
122
123impl Clickable for IconButton {
124 fn on_click(
125 mut self,
126 handler: impl Fn(&gpui::ClickEvent, &mut Window, &mut App) + 'static,
127 ) -> Self {
128 self.base = self.base.on_click(handler);
129 self
130 }
131
132 fn cursor_style(mut self, cursor_style: gpui::CursorStyle) -> Self {
133 self.base = self.base.cursor_style(cursor_style);
134 self
135 }
136}
137
138impl FixedWidth for IconButton {
139 fn width(mut self, width: impl Into<DefiniteLength>) -> Self {
140 self.base = self.base.width(width);
141 self
142 }
143
144 fn full_width(mut self) -> Self {
145 self.base = self.base.full_width();
146 self
147 }
148}
149
150impl ButtonCommon for IconButton {
151 fn id(&self) -> &ElementId {
152 self.base.id()
153 }
154
155 fn style(mut self, style: ButtonStyle) -> Self {
156 self.base = self.base.style(style);
157 self
158 }
159
160 fn size(mut self, size: ButtonSize) -> Self {
161 self.base = self.base.size(size);
162 self
163 }
164
165 fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
166 self.base = self.base.tooltip(tooltip);
167 self
168 }
169
170 fn tab_index(mut self, tab_index: impl Into<isize>) -> Self {
171 self.base = self.base.tab_index(tab_index);
172 self
173 }
174
175 fn layer(mut self, elevation: ElevationIndex) -> Self {
176 self.base = self.base.layer(elevation);
177 self
178 }
179
180 fn track_focus(mut self, focus_handle: &gpui::FocusHandle) -> Self {
181 self.base = self.base.track_focus(focus_handle);
182 self
183 }
184}
185
186impl VisibleOnHover for IconButton {
187 fn visible_on_hover(mut self, group_name: impl Into<SharedString>) -> Self {
188 self.base = self.base.visible_on_hover(group_name);
189 self
190 }
191}
192
193impl RenderOnce for IconButton {
194 #[allow(refining_impl_trait)]
195 fn render(self, window: &mut Window, cx: &mut App) -> ButtonLike {
196 let is_disabled = self.base.disabled;
197 let is_selected = self.base.selected;
198
199 let icon = self
200 .selected_icon
201 .filter(|_| is_selected)
202 .unwrap_or(self.icon);
203
204 let icon_color = if is_disabled {
205 Color::Disabled
206 } else if self.selected_style.is_some() && is_selected {
207 self.selected_style.unwrap().into()
208 } else if is_selected {
209 self.selected_icon_color.unwrap_or(Color::Selected)
210 } else {
211 let base_color = self.icon_color.color(cx);
212 Color::Custom(base_color.opacity(self.alpha.unwrap_or(1.0)))
213 };
214
215 let icon_element = Icon::new(icon).size(self.icon_size).color(icon_color);
216
217 self.base
218 .map(|this| match self.shape {
219 IconButtonShape::Square => {
220 let size = self.icon_size.square(window, cx);
221 this.width(size).height(size.into())
222 }
223 IconButtonShape::Wide => this,
224 })
225 .child(match self.indicator {
226 Some(indicator) => IconWithIndicator::new(icon_element, Some(indicator))
227 .indicator_border_color(self.indicator_border_color)
228 .into_any_element(),
229 None => icon_element.into_any_element(),
230 })
231 }
232}
233
234impl Component for IconButton {
235 fn scope() -> ComponentScope {
236 ComponentScope::Input
237 }
238
239 fn sort_name() -> &'static str {
240 "ButtonB"
241 }
242
243 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
244 Some(
245 v_flex()
246 .gap_6()
247 .children(vec![
248 example_group_with_title(
249 "Icon Button Styles",
250 vec![
251 single_example(
252 "Default",
253 IconButton::new("default", IconName::Check)
254 .layer(ElevationIndex::Background)
255 .into_any_element(),
256 ),
257 single_example(
258 "Filled",
259 IconButton::new("filled", IconName::Check)
260 .layer(ElevationIndex::Background)
261 .style(ButtonStyle::Filled)
262 .into_any_element(),
263 ),
264 single_example(
265 "Subtle",
266 IconButton::new("subtle", IconName::Check)
267 .layer(ElevationIndex::Background)
268 .style(ButtonStyle::Subtle)
269 .into_any_element(),
270 ),
271 single_example(
272 "Tinted",
273 IconButton::new("tinted", IconName::Check)
274 .layer(ElevationIndex::Background)
275 .style(ButtonStyle::Tinted(TintColor::Accent))
276 .into_any_element(),
277 ),
278 single_example(
279 "Transparent",
280 IconButton::new("transparent", IconName::Check)
281 .layer(ElevationIndex::Background)
282 .style(ButtonStyle::Transparent)
283 .into_any_element(),
284 ),
285 ],
286 ),
287 example_group_with_title(
288 "Icon Button Shapes",
289 vec![
290 single_example(
291 "Square",
292 IconButton::new("square", IconName::Check)
293 .shape(IconButtonShape::Square)
294 .style(ButtonStyle::Filled)
295 .layer(ElevationIndex::Background)
296 .into_any_element(),
297 ),
298 single_example(
299 "Wide",
300 IconButton::new("wide", IconName::Check)
301 .shape(IconButtonShape::Wide)
302 .style(ButtonStyle::Filled)
303 .layer(ElevationIndex::Background)
304 .into_any_element(),
305 ),
306 ],
307 ),
308 example_group_with_title(
309 "Icon Button Sizes",
310 vec![
311 single_example(
312 "XSmall",
313 IconButton::new("xsmall", IconName::Check)
314 .icon_size(IconSize::XSmall)
315 .style(ButtonStyle::Filled)
316 .layer(ElevationIndex::Background)
317 .into_any_element(),
318 ),
319 single_example(
320 "Small",
321 IconButton::new("small", IconName::Check)
322 .icon_size(IconSize::Small)
323 .style(ButtonStyle::Filled)
324 .layer(ElevationIndex::Background)
325 .into_any_element(),
326 ),
327 single_example(
328 "Medium",
329 IconButton::new("medium", IconName::Check)
330 .icon_size(IconSize::Medium)
331 .style(ButtonStyle::Filled)
332 .layer(ElevationIndex::Background)
333 .into_any_element(),
334 ),
335 single_example(
336 "XLarge",
337 IconButton::new("xlarge", IconName::Check)
338 .icon_size(IconSize::XLarge)
339 .style(ButtonStyle::Filled)
340 .layer(ElevationIndex::Background)
341 .into_any_element(),
342 ),
343 ],
344 ),
345 example_group_with_title(
346 "Special States",
347 vec![
348 single_example(
349 "Disabled",
350 IconButton::new("disabled", IconName::Check)
351 .disabled(true)
352 .style(ButtonStyle::Filled)
353 .layer(ElevationIndex::Background)
354 .into_any_element(),
355 ),
356 single_example(
357 "Selected",
358 IconButton::new("selected", IconName::Check)
359 .toggle_state(true)
360 .style(ButtonStyle::Filled)
361 .layer(ElevationIndex::Background)
362 .into_any_element(),
363 ),
364 single_example(
365 "With Indicator",
366 IconButton::new("indicator", IconName::Check)
367 .indicator(Indicator::dot().color(Color::Success))
368 .style(ButtonStyle::Filled)
369 .layer(ElevationIndex::Background)
370 .into_any_element(),
371 ),
372 ],
373 ),
374 example_group_with_title(
375 "Custom Colors",
376 vec![
377 single_example(
378 "Custom Icon Color",
379 IconButton::new("custom_color", IconName::Check)
380 .icon_color(Color::Accent)
381 .style(ButtonStyle::Filled)
382 .layer(ElevationIndex::Background)
383 .into_any_element(),
384 ),
385 single_example(
386 "With Alpha",
387 IconButton::new("alpha", IconName::Check)
388 .alpha(0.5)
389 .style(ButtonStyle::Filled)
390 .layer(ElevationIndex::Background)
391 .into_any_element(),
392 ),
393 ],
394 ),
395 ])
396 .into_any_element(),
397 )
398 }
399}