icon_button.rs

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