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#[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 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 if matches!(self.base.style, ButtonStyle::Tinted(_)) {
211 Color::Custom(gpui::white().opacity(self.alpha.unwrap_or(1.0)))
213 } else {
214 let base_color = self.icon_color.color(cx);
215 Color::Custom(base_color.opacity(self.alpha.unwrap_or(1.0)))
216 };
217
218 let icon_element = Icon::new(icon).size(self.icon_size).color(icon_color);
219
220 self.base
221 .map(|this| match self.shape {
222 IconButtonShape::Square => {
223 let size = self.icon_size.square(window, cx);
224 this.width(size).height(size.into())
225 }
226 IconButtonShape::Wide => this,
227 })
228 .child(match self.indicator {
229 Some(indicator) => IconWithIndicator::new(icon_element, Some(indicator))
230 .indicator_border_color(self.indicator_border_color)
231 .into_any_element(),
232 None => icon_element.into_any_element(),
233 })
234 }
235}
236
237impl Component for IconButton {
238 fn scope() -> ComponentScope {
239 ComponentScope::Input
240 }
241
242 fn sort_name() -> &'static str {
243 "ButtonB"
244 }
245
246 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
247 Some(
248 v_flex()
249 .gap_6()
250 .children(vec![
251 example_group_with_title(
252 "Icon Button Styles",
253 vec![
254 single_example(
255 "Default",
256 IconButton::new("default", IconName::Check)
257 .layer(ElevationIndex::Background)
258 .into_any_element(),
259 ),
260 single_example(
261 "Filled",
262 IconButton::new("filled", IconName::Check)
263 .layer(ElevationIndex::Background)
264 .style(ButtonStyle::Filled)
265 .into_any_element(),
266 ),
267 single_example(
268 "Subtle",
269 IconButton::new("subtle", IconName::Check)
270 .layer(ElevationIndex::Background)
271 .style(ButtonStyle::Subtle)
272 .into_any_element(),
273 ),
274 single_example(
275 "Tinted",
276 IconButton::new("tinted", IconName::Check)
277 .layer(ElevationIndex::Background)
278 .style(ButtonStyle::Tinted(TintColor::Accent))
279 .into_any_element(),
280 ),
281 single_example(
282 "Transparent",
283 IconButton::new("transparent", IconName::Check)
284 .layer(ElevationIndex::Background)
285 .style(ButtonStyle::Transparent)
286 .into_any_element(),
287 ),
288 ],
289 ),
290 example_group_with_title(
291 "Icon Button Shapes",
292 vec![
293 single_example(
294 "Square",
295 IconButton::new("square", IconName::Check)
296 .shape(IconButtonShape::Square)
297 .style(ButtonStyle::Filled)
298 .layer(ElevationIndex::Background)
299 .into_any_element(),
300 ),
301 single_example(
302 "Wide",
303 IconButton::new("wide", IconName::Check)
304 .shape(IconButtonShape::Wide)
305 .style(ButtonStyle::Filled)
306 .layer(ElevationIndex::Background)
307 .into_any_element(),
308 ),
309 ],
310 ),
311 example_group_with_title(
312 "Icon Button Sizes",
313 vec![
314 single_example(
315 "XSmall",
316 IconButton::new("xsmall", IconName::Check)
317 .icon_size(IconSize::XSmall)
318 .style(ButtonStyle::Filled)
319 .layer(ElevationIndex::Background)
320 .into_any_element(),
321 ),
322 single_example(
323 "Small",
324 IconButton::new("small", IconName::Check)
325 .icon_size(IconSize::Small)
326 .style(ButtonStyle::Filled)
327 .layer(ElevationIndex::Background)
328 .into_any_element(),
329 ),
330 single_example(
331 "Medium",
332 IconButton::new("medium", IconName::Check)
333 .icon_size(IconSize::Medium)
334 .style(ButtonStyle::Filled)
335 .layer(ElevationIndex::Background)
336 .into_any_element(),
337 ),
338 single_example(
339 "XLarge",
340 IconButton::new("xlarge", IconName::Check)
341 .icon_size(IconSize::XLarge)
342 .style(ButtonStyle::Filled)
343 .layer(ElevationIndex::Background)
344 .into_any_element(),
345 ),
346 ],
347 ),
348 example_group_with_title(
349 "Special States",
350 vec![
351 single_example(
352 "Disabled",
353 IconButton::new("disabled", IconName::Check)
354 .disabled(true)
355 .style(ButtonStyle::Filled)
356 .layer(ElevationIndex::Background)
357 .into_any_element(),
358 ),
359 single_example(
360 "Selected",
361 IconButton::new("selected", IconName::Check)
362 .toggle_state(true)
363 .style(ButtonStyle::Filled)
364 .layer(ElevationIndex::Background)
365 .into_any_element(),
366 ),
367 single_example(
368 "With Indicator",
369 IconButton::new("indicator", IconName::Check)
370 .indicator(Indicator::dot().color(Color::Success))
371 .style(ButtonStyle::Filled)
372 .layer(ElevationIndex::Background)
373 .into_any_element(),
374 ),
375 ],
376 ),
377 example_group_with_title(
378 "Custom Colors",
379 vec![
380 single_example(
381 "Custom Icon Color",
382 IconButton::new("custom_color", IconName::Check)
383 .icon_color(Color::Accent)
384 .style(ButtonStyle::Filled)
385 .layer(ElevationIndex::Background)
386 .into_any_element(),
387 ),
388 single_example(
389 "With Alpha",
390 IconButton::new("alpha", IconName::Check)
391 .alpha(0.5)
392 .style(ButtonStyle::Filled)
393 .layer(ElevationIndex::Background)
394 .into_any_element(),
395 ),
396 ],
397 ),
398 ])
399 .into_any_element(),
400 )
401 }
402}