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