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 const fn shape(mut self, shape: IconButtonShape) -> Self {
 49        self.shape = shape;
 50        self
 51    }
 52
 53    pub const fn icon_size(mut self, icon_size: IconSize) -> Self {
 54        self.icon_size = icon_size;
 55        self
 56    }
 57
 58    pub const fn icon_color(mut self, icon_color: Color) -> Self {
 59        self.icon_color = icon_color;
 60        self
 61    }
 62
 63    pub const 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 const 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                            single_example(
241                                "Default",
242                                IconButton::new("default", IconName::Check)
243                                    .layer(ElevationIndex::Background)
244                                    .into_any_element(),
245                            ),
246                            single_example(
247                                "Filled",
248                                IconButton::new("filled", IconName::Check)
249                                    .layer(ElevationIndex::Background)
250                                    .style(ButtonStyle::Filled)
251                                    .into_any_element(),
252                            ),
253                            single_example(
254                                "Subtle",
255                                IconButton::new("subtle", IconName::Check)
256                                    .layer(ElevationIndex::Background)
257                                    .style(ButtonStyle::Subtle)
258                                    .into_any_element(),
259                            ),
260                            single_example(
261                                "Tinted",
262                                IconButton::new("tinted", IconName::Check)
263                                    .layer(ElevationIndex::Background)
264                                    .style(ButtonStyle::Tinted(TintColor::Accent))
265                                    .into_any_element(),
266                            ),
267                            single_example(
268                                "Transparent",
269                                IconButton::new("transparent", IconName::Check)
270                                    .layer(ElevationIndex::Background)
271                                    .style(ButtonStyle::Transparent)
272                                    .into_any_element(),
273                            ),
274                        ],
275                    ),
276                    example_group_with_title(
277                        "Icon Button Shapes",
278                        vec![
279                            single_example(
280                                "Square",
281                                IconButton::new("square", IconName::Check)
282                                    .shape(IconButtonShape::Square)
283                                    .style(ButtonStyle::Filled)
284                                    .layer(ElevationIndex::Background)
285                                    .into_any_element(),
286                            ),
287                            single_example(
288                                "Wide",
289                                IconButton::new("wide", IconName::Check)
290                                    .shape(IconButtonShape::Wide)
291                                    .style(ButtonStyle::Filled)
292                                    .layer(ElevationIndex::Background)
293                                    .into_any_element(),
294                            ),
295                        ],
296                    ),
297                    example_group_with_title(
298                        "Icon Button Sizes",
299                        vec![
300                            single_example(
301                                "XSmall",
302                                IconButton::new("xsmall", IconName::Check)
303                                    .icon_size(IconSize::XSmall)
304                                    .style(ButtonStyle::Filled)
305                                    .layer(ElevationIndex::Background)
306                                    .into_any_element(),
307                            ),
308                            single_example(
309                                "Small",
310                                IconButton::new("small", IconName::Check)
311                                    .icon_size(IconSize::Small)
312                                    .style(ButtonStyle::Filled)
313                                    .layer(ElevationIndex::Background)
314                                    .into_any_element(),
315                            ),
316                            single_example(
317                                "Medium",
318                                IconButton::new("medium", IconName::Check)
319                                    .icon_size(IconSize::Medium)
320                                    .style(ButtonStyle::Filled)
321                                    .layer(ElevationIndex::Background)
322                                    .into_any_element(),
323                            ),
324                            single_example(
325                                "XLarge",
326                                IconButton::new("xlarge", IconName::Check)
327                                    .icon_size(IconSize::XLarge)
328                                    .style(ButtonStyle::Filled)
329                                    .layer(ElevationIndex::Background)
330                                    .into_any_element(),
331                            ),
332                        ],
333                    ),
334                    example_group_with_title(
335                        "Special States",
336                        vec![
337                            single_example(
338                                "Disabled",
339                                IconButton::new("disabled", IconName::Check)
340                                    .disabled(true)
341                                    .style(ButtonStyle::Filled)
342                                    .layer(ElevationIndex::Background)
343                                    .into_any_element(),
344                            ),
345                            single_example(
346                                "Selected",
347                                IconButton::new("selected", IconName::Check)
348                                    .toggle_state(true)
349                                    .style(ButtonStyle::Filled)
350                                    .layer(ElevationIndex::Background)
351                                    .into_any_element(),
352                            ),
353                            single_example(
354                                "With Indicator",
355                                IconButton::new("indicator", IconName::Check)
356                                    .indicator(Indicator::dot().color(Color::Success))
357                                    .style(ButtonStyle::Filled)
358                                    .layer(ElevationIndex::Background)
359                                    .into_any_element(),
360                            ),
361                        ],
362                    ),
363                    example_group_with_title(
364                        "Custom Colors",
365                        vec![
366                            single_example(
367                                "Custom Icon Color",
368                                IconButton::new("custom_color", IconName::Check)
369                                    .icon_color(Color::Accent)
370                                    .style(ButtonStyle::Filled)
371                                    .layer(ElevationIndex::Background)
372                                    .into_any_element(),
373                            ),
374                            single_example(
375                                "With Alpha",
376                                IconButton::new("alpha", IconName::Check)
377                                    .alpha(0.5)
378                                    .style(ButtonStyle::Filled)
379                                    .layer(ElevationIndex::Background)
380                                    .into_any_element(),
381                            ),
382                        ],
383                    ),
384                ])
385                .into_any_element(),
386        )
387    }
388}