button.rs

  1#![allow(missing_docs)]
  2use gpui::{AnyView, DefiniteLength};
  3
  4use crate::{
  5    prelude::*, Color, DynamicSpacing, ElevationIndex, IconPosition, KeyBinding, TintColor,
  6};
  7use crate::{
  8    ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize, Label, LineHeightStyle,
  9};
 10
 11use super::button_icon::ButtonIcon;
 12
 13/// An element that creates a button with a label and an optional icon.
 14///
 15/// Common buttons:
 16/// - Label, Icon + Label: [`Button`] (this component)
 17/// - Icon only: [`IconButton`]
 18/// - Custom: [`ButtonLike`]
 19///
 20/// To create a more complex button than what the [`Button`] or [`IconButton`] components provide, use
 21/// [`ButtonLike`] directly.
 22///
 23/// # Examples
 24///
 25/// **A button with a label**, is typically used in scenarios such as a form, where the button's label
 26/// indicates what action will be performed when the button is clicked.
 27///
 28/// ```
 29/// use ui::prelude::*;
 30///
 31/// Button::new("button_id", "Click me!")
 32///     .on_click(|event, cx| {
 33///         // Handle click event
 34///     });
 35/// ```
 36///
 37/// **A toggleable button**, is typically used in scenarios such as a toolbar,
 38/// where the button's state indicates whether a feature is enabled or not, or
 39/// a trigger for a popover menu, where clicking the button toggles the visibility of the menu.
 40///
 41/// ```
 42/// use ui::prelude::*;
 43///
 44/// Button::new("button_id", "Click me!")
 45///     .icon(IconName::Check)
 46///     .selected(true)
 47///     .on_click(|event, cx| {
 48///         // Handle click event
 49///     });
 50/// ```
 51///
 52/// To change the style of the button when it is selected use the [`selected_style`][Button::selected_style] method.
 53///
 54/// ```
 55/// use ui::prelude::*;
 56/// use ui::TintColor;
 57///
 58/// Button::new("button_id", "Click me!")
 59///     .selected(true)
 60///     .selected_style(ButtonStyle::Tinted(TintColor::Accent))
 61///     .on_click(|event, cx| {
 62///         // Handle click event
 63///     });
 64/// ```
 65/// This will create a button with a blue tinted background when selected.
 66///
 67/// **A full-width button**, is typically used in scenarios such as the bottom of a modal or form, where it occupies the entire width of its container.
 68/// The button's content, including text and icons, is centered by default.
 69///
 70/// ```
 71/// use ui::prelude::*;
 72///
 73/// let button = Button::new("button_id", "Click me!")
 74///     .full_width()
 75///     .on_click(|event, cx| {
 76///         // Handle click event
 77///     });
 78/// ```
 79///
 80#[derive(IntoElement)]
 81pub struct Button {
 82    base: ButtonLike,
 83    label: SharedString,
 84    label_color: Option<Color>,
 85    label_size: Option<LabelSize>,
 86    selected_label: Option<SharedString>,
 87    selected_label_color: Option<Color>,
 88    icon: Option<IconName>,
 89    icon_position: Option<IconPosition>,
 90    icon_size: Option<IconSize>,
 91    icon_color: Option<Color>,
 92    selected_icon: Option<IconName>,
 93    selected_icon_color: Option<Color>,
 94    key_binding: Option<KeyBinding>,
 95    alpha: Option<f32>,
 96}
 97
 98impl Button {
 99    /// Creates a new [`Button`] with a specified identifier and label.
100    ///
101    /// This is the primary constructor for a [`Button`] component. It initializes
102    /// the button with the provided identifier and label text, setting all other
103    /// properties to their default values, which can be customized using the
104    /// builder pattern methods provided by this struct.
105    pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
106        Self {
107            base: ButtonLike::new(id),
108            label: label.into(),
109            label_color: None,
110            label_size: None,
111            selected_label: None,
112            selected_label_color: None,
113            icon: None,
114            icon_position: None,
115            icon_size: None,
116            icon_color: None,
117            selected_icon: None,
118            selected_icon_color: None,
119            key_binding: None,
120            alpha: None,
121        }
122    }
123
124    /// Sets the color of the button's label.
125    pub fn color(mut self, label_color: impl Into<Option<Color>>) -> Self {
126        self.label_color = label_color.into();
127        self
128    }
129
130    /// Defines the size of the button's label.
131    pub fn label_size(mut self, label_size: impl Into<Option<LabelSize>>) -> Self {
132        self.label_size = label_size.into();
133        self
134    }
135
136    /// Sets the label used when the button is in a selected state.
137    pub fn selected_label<L: Into<SharedString>>(mut self, label: impl Into<Option<L>>) -> Self {
138        self.selected_label = label.into().map(Into::into);
139        self
140    }
141
142    /// Sets the label color used when the button is in a selected state.
143    pub fn selected_label_color(mut self, color: impl Into<Option<Color>>) -> Self {
144        self.selected_label_color = color.into();
145        self
146    }
147
148    /// Assigns an icon to the button.
149    pub fn icon(mut self, icon: impl Into<Option<IconName>>) -> Self {
150        self.icon = icon.into();
151        self
152    }
153
154    /// Sets the position of the icon relative to the label.
155    pub fn icon_position(mut self, icon_position: impl Into<Option<IconPosition>>) -> Self {
156        self.icon_position = icon_position.into();
157        self
158    }
159
160    /// Specifies the size of the button's icon.
161    pub fn icon_size(mut self, icon_size: impl Into<Option<IconSize>>) -> Self {
162        self.icon_size = icon_size.into();
163        self
164    }
165
166    /// Sets the color of the button's icon.
167    pub fn icon_color(mut self, icon_color: impl Into<Option<Color>>) -> Self {
168        self.icon_color = icon_color.into();
169        self
170    }
171
172    /// Chooses an icon to display when the button is in a selected state.
173    pub fn selected_icon(mut self, icon: impl Into<Option<IconName>>) -> Self {
174        self.selected_icon = icon.into();
175        self
176    }
177
178    /// Sets the icon color used when the button is in a selected state.
179    pub fn selected_icon_color(mut self, color: impl Into<Option<Color>>) -> Self {
180        self.selected_icon_color = color.into();
181        self
182    }
183
184    /// Display the keybinding that triggers the button action.
185    pub fn key_binding(mut self, key_binding: impl Into<Option<KeyBinding>>) -> Self {
186        self.key_binding = key_binding.into();
187        self
188    }
189
190    /// Sets the alpha property of the color of label.
191    pub fn alpha(mut self, alpha: f32) -> Self {
192        self.alpha = Some(alpha);
193        self
194    }
195}
196
197impl Toggleable for Button {
198    /// Sets the selected state of the button.
199    ///
200    /// This method allows the selection state of the button to be specified.
201    /// It modifies the button's appearance to reflect its selected state.
202    ///
203    /// # Examples
204    ///
205    /// ```
206    /// use ui::prelude::*;
207    ///
208    /// Button::new("button_id", "Click me!")
209    ///     .selected(true)
210    ///     .on_click(|event, cx| {
211    ///         // Handle click event
212    ///     });
213    /// ```
214    ///
215    /// Use [`selected_style`](Button::selected_style) to change the style of the button when it is selected.
216    fn toggle_state(mut self, selected: bool) -> Self {
217        self.base = self.base.toggle_state(selected);
218        self
219    }
220}
221
222impl SelectableButton for Button {
223    /// Sets the style for the button when selected.
224    ///
225    /// # Examples
226    ///
227    /// ```
228    /// use ui::prelude::*;
229    /// use ui::TintColor;
230    ///
231    /// Button::new("button_id", "Click me!")
232    ///     .selected(true)
233    ///     .selected_style(ButtonStyle::Tinted(TintColor::Accent))
234    ///     .on_click(|event, cx| {
235    ///         // Handle click event
236    ///     });
237    /// ```
238    /// This results in a button with a blue tinted background when selected.
239    fn selected_style(mut self, style: ButtonStyle) -> Self {
240        self.base = self.base.selected_style(style);
241        self
242    }
243}
244
245impl Disableable for Button {
246    /// Disables the button.
247    ///
248    /// This method allows the button to be disabled. When a button is disabled,
249    /// it doesn't react to user interactions and its appearance is updated to reflect this.
250    ///
251    /// # Examples
252    ///
253    /// ```
254    /// use ui::prelude::*;
255    ///
256    /// Button::new("button_id", "Click me!")
257    ///     .disabled(true)
258    ///     .on_click(|event, cx| {
259    ///         // Handle click event
260    ///     });
261    /// ```
262    ///
263    /// This results in a button that is disabled and does not respond to click events.
264    fn disabled(mut self, disabled: bool) -> Self {
265        self.base = self.base.disabled(disabled);
266        self
267    }
268}
269
270impl Clickable for Button {
271    /// Sets the click event handler for the button.
272    fn on_click(
273        mut self,
274        handler: impl Fn(&gpui::ClickEvent, &mut Window, &mut App) + 'static,
275    ) -> Self {
276        self.base = self.base.on_click(handler);
277        self
278    }
279
280    fn cursor_style(mut self, cursor_style: gpui::CursorStyle) -> Self {
281        self.base = self.base.cursor_style(cursor_style);
282        self
283    }
284}
285
286impl FixedWidth for Button {
287    /// Sets a fixed width for the button.
288    ///
289    /// This function allows a button to have a fixed width instead of automatically growing or shrinking.
290    /// Sets a fixed width for the button.
291    ///
292    /// # Examples
293    ///
294    /// ```
295    /// use ui::prelude::*;
296    ///
297    /// Button::new("button_id", "Click me!")
298    ///     .width(px(100.).into())
299    ///     .on_click(|event, cx| {
300    ///         // Handle click event
301    ///     });
302    /// ```
303    ///
304    /// This sets the button's width to be exactly 100 pixels.
305    fn width(mut self, width: DefiniteLength) -> Self {
306        self.base = self.base.width(width);
307        self
308    }
309
310    /// Sets the button to occupy the full width of its container.
311    ///
312    /// # Examples
313    ///
314    /// ```
315    /// use ui::prelude::*;
316    ///
317    /// Button::new("button_id", "Click me!")
318    ///     .full_width()
319    ///     .on_click(|event, cx| {
320    ///         // Handle click event
321    ///     });
322    /// ```
323    ///
324    /// This stretches the button to the full width of its container.
325    fn full_width(mut self) -> Self {
326        self.base = self.base.full_width();
327        self
328    }
329}
330
331impl ButtonCommon for Button {
332    /// Sets the button's id.
333    fn id(&self) -> &ElementId {
334        self.base.id()
335    }
336
337    /// Sets the visual style of the button using a [`ButtonStyle`].
338    fn style(mut self, style: ButtonStyle) -> Self {
339        self.base = self.base.style(style);
340        self
341    }
342
343    /// Sets the button's size using a [`ButtonSize`].
344    fn size(mut self, size: ButtonSize) -> Self {
345        self.base = self.base.size(size);
346        self
347    }
348
349    /// Sets a tooltip for the button.
350    ///
351    /// This method allows a tooltip to be set for the button. The tooltip is a function that
352    /// takes a mutable references to [`Window`] and [`App`], and returns an [`AnyView`]. The
353    /// tooltip is displayed when the user hovers over the button.
354    ///
355    /// # Examples
356    ///
357    /// ```
358    /// use ui::prelude::*;
359    /// use ui::Tooltip;
360    ///
361    /// Button::new("button_id", "Click me!")
362    ///     .tooltip(Tooltip::text_f("This is a tooltip", cx))
363    ///     .on_click(|event, cx| {
364    ///         // Handle click event
365    ///     });
366    /// ```
367    ///
368    /// This will create a button with a tooltip that displays "This is a tooltip" when hovered over.
369    fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
370        self.base = self.base.tooltip(tooltip);
371        self
372    }
373
374    fn layer(mut self, elevation: ElevationIndex) -> Self {
375        self.base = self.base.layer(elevation);
376        self
377    }
378}
379
380impl RenderOnce for Button {
381    #[allow(refining_impl_trait)]
382    fn render(self, _window: &mut Window, cx: &mut App) -> ButtonLike {
383        let is_disabled = self.base.disabled;
384        let is_selected = self.base.selected;
385
386        let label = self
387            .selected_label
388            .filter(|_| is_selected)
389            .unwrap_or(self.label);
390
391        let label_color = if is_disabled {
392            Color::Disabled
393        } else if is_selected {
394            self.selected_label_color.unwrap_or(Color::Selected)
395        } else {
396            self.label_color.unwrap_or_default()
397        };
398
399        self.base.child(
400            h_flex()
401                .gap(DynamicSpacing::Base04.rems(cx))
402                .when(self.icon_position == Some(IconPosition::Start), |this| {
403                    this.children(self.icon.map(|icon| {
404                        ButtonIcon::new(icon)
405                            .disabled(is_disabled)
406                            .toggle_state(is_selected)
407                            .selected_icon(self.selected_icon)
408                            .selected_icon_color(self.selected_icon_color)
409                            .size(self.icon_size)
410                            .color(self.icon_color)
411                    }))
412                })
413                .child(
414                    h_flex()
415                        .gap(DynamicSpacing::Base06.rems(cx))
416                        .justify_between()
417                        .child(
418                            Label::new(label)
419                                .color(label_color)
420                                .size(self.label_size.unwrap_or_default())
421                                .when_some(self.alpha, |this, alpha| this.alpha(alpha))
422                                .line_height_style(LineHeightStyle::UiLabel),
423                        )
424                        .children(self.key_binding),
425                )
426                .when(self.icon_position != Some(IconPosition::Start), |this| {
427                    this.children(self.icon.map(|icon| {
428                        ButtonIcon::new(icon)
429                            .disabled(is_disabled)
430                            .toggle_state(is_selected)
431                            .selected_icon(self.selected_icon)
432                            .selected_icon_color(self.selected_icon_color)
433                            .size(self.icon_size)
434                            .color(self.icon_color)
435                    }))
436                }),
437        )
438    }
439}
440
441impl ComponentPreview for Button {
442    fn description() -> impl Into<Option<&'static str>> {
443        "A button allows users to take actions, and make choices, with a single tap."
444    }
445
446    fn examples(_window: &mut Window, _: &mut App) -> Vec<ComponentExampleGroup<Self>> {
447        vec![
448            example_group_with_title(
449                "Styles",
450                vec![
451                    single_example("Default", Button::new("default", "Default")),
452                    single_example(
453                        "Filled",
454                        Button::new("filled", "Filled").style(ButtonStyle::Filled),
455                    ),
456                    single_example(
457                        "Subtle",
458                        Button::new("outline", "Subtle").style(ButtonStyle::Subtle),
459                    ),
460                    single_example(
461                        "Transparent",
462                        Button::new("transparent", "Transparent").style(ButtonStyle::Transparent),
463                    ),
464                ],
465            ),
466            example_group_with_title(
467                "Tinted",
468                vec![
469                    single_example(
470                        "Accent",
471                        Button::new("tinted_accent", "Accent")
472                            .style(ButtonStyle::Tinted(TintColor::Accent)),
473                    ),
474                    single_example(
475                        "Error",
476                        Button::new("tinted_negative", "Error")
477                            .style(ButtonStyle::Tinted(TintColor::Error)),
478                    ),
479                    single_example(
480                        "Warning",
481                        Button::new("tinted_warning", "Warning")
482                            .style(ButtonStyle::Tinted(TintColor::Warning)),
483                    ),
484                    single_example(
485                        "Success",
486                        Button::new("tinted_positive", "Success")
487                            .style(ButtonStyle::Tinted(TintColor::Success)),
488                    ),
489                ],
490            ),
491            example_group_with_title(
492                "States",
493                vec![
494                    single_example("Default", Button::new("default_state", "Default")),
495                    single_example(
496                        "Disabled",
497                        Button::new("disabled", "Disabled").disabled(true),
498                    ),
499                    single_example(
500                        "Selected",
501                        Button::new("selected", "Selected").toggle_state(true),
502                    ),
503                ],
504            ),
505            example_group_with_title(
506                "With Icons",
507                vec![
508                    single_example(
509                        "Icon Start",
510                        Button::new("icon_start", "Icon Start")
511                            .icon(IconName::Check)
512                            .icon_position(IconPosition::Start),
513                    ),
514                    single_example(
515                        "Icon End",
516                        Button::new("icon_end", "Icon End")
517                            .icon(IconName::Check)
518                            .icon_position(IconPosition::End),
519                    ),
520                    single_example(
521                        "Icon Color",
522                        Button::new("icon_color", "Icon Color")
523                            .icon(IconName::Check)
524                            .icon_color(Color::Accent),
525                    ),
526                    single_example(
527                        "Tinted Icons",
528                        Button::new("tinted_icons", "Error")
529                            .style(ButtonStyle::Tinted(TintColor::Error))
530                            .color(Color::Error)
531                            .icon_color(Color::Error)
532                            .icon(IconName::Trash)
533                            .icon_position(IconPosition::Start),
534                    ),
535                ],
536            ),
537        ]
538    }
539}