button.rs

  1#![allow(missing_docs)]
  2use gpui::{AnyView, DefiniteLength};
  3
  4use crate::{prelude::*, ElevationIndex, IconPosition, KeyBinding, Spacing};
  5use crate::{
  6    ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize, Label, LineHeightStyle,
  7};
  8
  9use super::button_icon::ButtonIcon;
 10
 11/// An element that creates a button with a label and an optional icon.
 12///
 13/// Common buttons:
 14/// - Label, Icon + Label: [`Button`] (this component)
 15/// - Icon only: [`IconButton`]
 16/// - Custom: [`ButtonLike`]
 17///
 18/// To create a more complex button than what the [`Button`] or [`IconButton`] components provide, use
 19/// [`ButtonLike`] directly.
 20///
 21/// # Examples
 22///
 23/// **A button with a label**, is typically used in scenarios such as a form, where the button's label
 24/// indicates what action will be performed when the button is clicked.
 25///
 26/// ```
 27/// use ui::prelude::*;
 28///
 29/// Button::new("button_id", "Click me!")
 30///     .on_click(|event, cx| {
 31///         // Handle click event
 32///     });
 33/// ```
 34///
 35/// **A toggleable button**, is typically used in scenarios such as a toolbar,
 36/// where the button's state indicates whether a feature is enabled or not, or
 37/// a trigger for a popover menu, where clicking the button toggles the visibility of the menu.
 38///
 39/// ```
 40/// use ui::prelude::*;
 41///
 42/// Button::new("button_id", "Click me!")
 43///     .icon(IconName::Check)
 44///     .selected(true)
 45///     .on_click(|event, cx| {
 46///         // Handle click event
 47///     });
 48/// ```
 49///
 50/// To change the style of the button when it is selected use the [`selected_style`][Button::selected_style] method.
 51///
 52/// ```
 53/// use ui::prelude::*;
 54/// use ui::TintColor;
 55///
 56/// Button::new("button_id", "Click me!")
 57///     .selected(true)
 58///     .selected_style(ButtonStyle::Tinted(TintColor::Accent))
 59///     .on_click(|event, cx| {
 60///         // Handle click event
 61///     });
 62/// ```
 63/// This will create a button with a blue tinted background when selected.
 64///
 65/// **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.
 66/// The button's content, including text and icons, is centered by default.
 67///
 68/// ```
 69/// use ui::prelude::*;
 70///
 71/// let button = Button::new("button_id", "Click me!")
 72///     .full_width()
 73///     .on_click(|event, cx| {
 74///         // Handle click event
 75///     });
 76/// ```
 77///
 78#[derive(IntoElement)]
 79pub struct Button {
 80    base: ButtonLike,
 81    label: SharedString,
 82    label_color: Option<Color>,
 83    label_size: Option<LabelSize>,
 84    selected_label: Option<SharedString>,
 85    selected_label_color: Option<Color>,
 86    icon: Option<IconName>,
 87    icon_position: Option<IconPosition>,
 88    icon_size: Option<IconSize>,
 89    icon_color: Option<Color>,
 90    selected_icon: Option<IconName>,
 91    selected_icon_color: Option<Color>,
 92    key_binding: Option<KeyBinding>,
 93    alpha: Option<f32>,
 94}
 95
 96impl Button {
 97    /// Creates a new [`Button`] with a specified identifier and label.
 98    ///
 99    /// This is the primary constructor for a [`Button`] component. It initializes
100    /// the button with the provided identifier and label text, setting all other
101    /// properties to their default values, which can be customized using the
102    /// builder pattern methods provided by this struct.
103    pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
104        Self {
105            base: ButtonLike::new(id),
106            label: label.into(),
107            label_color: None,
108            label_size: None,
109            selected_label: None,
110            selected_label_color: None,
111            icon: None,
112            icon_position: None,
113            icon_size: None,
114            icon_color: None,
115            selected_icon: None,
116            selected_icon_color: None,
117            key_binding: None,
118            alpha: None,
119        }
120    }
121
122    /// Sets the color of the button's label.
123    pub fn color(mut self, label_color: impl Into<Option<Color>>) -> Self {
124        self.label_color = label_color.into();
125        self
126    }
127
128    /// Defines the size of the button's label.
129    pub fn label_size(mut self, label_size: impl Into<Option<LabelSize>>) -> Self {
130        self.label_size = label_size.into();
131        self
132    }
133
134    /// Sets the label used when the button is in a selected state.
135    pub fn selected_label<L: Into<SharedString>>(mut self, label: impl Into<Option<L>>) -> Self {
136        self.selected_label = label.into().map(Into::into);
137        self
138    }
139
140    /// Sets the label color used when the button is in a selected state.
141    pub fn selected_label_color(mut self, color: impl Into<Option<Color>>) -> Self {
142        self.selected_label_color = color.into();
143        self
144    }
145
146    /// Assigns an icon to the button.
147    pub fn icon(mut self, icon: impl Into<Option<IconName>>) -> Self {
148        self.icon = icon.into();
149        self
150    }
151
152    /// Sets the position of the icon relative to the label.
153    pub fn icon_position(mut self, icon_position: impl Into<Option<IconPosition>>) -> Self {
154        self.icon_position = icon_position.into();
155        self
156    }
157
158    /// Specifies the size of the button's icon.
159    pub fn icon_size(mut self, icon_size: impl Into<Option<IconSize>>) -> Self {
160        self.icon_size = icon_size.into();
161        self
162    }
163
164    /// Sets the color of the button's icon.
165    pub fn icon_color(mut self, icon_color: impl Into<Option<Color>>) -> Self {
166        self.icon_color = icon_color.into();
167        self
168    }
169
170    /// Chooses an icon to display when the button is in a selected state.
171    pub fn selected_icon(mut self, icon: impl Into<Option<IconName>>) -> Self {
172        self.selected_icon = icon.into();
173        self
174    }
175
176    /// Sets the icon color used when the button is in a selected state.
177    pub fn selected_icon_color(mut self, color: impl Into<Option<Color>>) -> Self {
178        self.selected_icon_color = color.into();
179        self
180    }
181
182    /// Binds a key combination to the button for keyboard shortcuts.
183    pub fn key_binding(mut self, key_binding: impl Into<Option<KeyBinding>>) -> Self {
184        self.key_binding = key_binding.into();
185        self
186    }
187
188    /// Sets the alpha property of the color of label.
189    pub fn alpha(mut self, alpha: f32) -> Self {
190        self.alpha = Some(alpha);
191        self
192    }
193}
194
195impl Selectable for Button {
196    /// Sets the selected state of the button.
197    ///
198    /// This method allows the selection state of the button to be specified.
199    /// It modifies the button's appearance to reflect its selected state.
200    ///
201    /// # Examples
202    ///
203    /// ```
204    /// use ui::prelude::*;
205    ///
206    /// Button::new("button_id", "Click me!")
207    ///     .selected(true)
208    ///     .on_click(|event, cx| {
209    ///         // Handle click event
210    ///     });
211    /// ```
212    ///
213    /// Use [`selected_style`](Button::selected_style) to change the style of the button when it is selected.
214    fn selected(mut self, selected: bool) -> Self {
215        self.base = self.base.selected(selected);
216        self
217    }
218}
219
220impl SelectableButton for Button {
221    /// Sets the style for the button when selected.
222    ///
223    /// # Examples
224    ///
225    /// ```
226    /// use ui::prelude::*;
227    /// use ui::TintColor;
228    ///
229    /// Button::new("button_id", "Click me!")
230    ///     .selected(true)
231    ///     .selected_style(ButtonStyle::Tinted(TintColor::Accent))
232    ///     .on_click(|event, cx| {
233    ///         // Handle click event
234    ///     });
235    /// ```
236    /// This results in a button with a blue tinted background when selected.
237    fn selected_style(mut self, style: ButtonStyle) -> Self {
238        self.base = self.base.selected_style(style);
239        self
240    }
241}
242
243impl Disableable for Button {
244    /// Disables the button.
245    ///
246    /// This method allows the button to be disabled. When a button is disabled,
247    /// it doesn't react to user interactions and its appearance is updated to reflect this.
248    ///
249    /// # Examples
250    ///
251    /// ```
252    /// use ui::prelude::*;
253    ///
254    /// Button::new("button_id", "Click me!")
255    ///     .disabled(true)
256    ///     .on_click(|event, cx| {
257    ///         // Handle click event
258    ///     });
259    /// ```
260    ///
261    /// This results in a button that is disabled and does not respond to click events.
262    fn disabled(mut self, disabled: bool) -> Self {
263        self.base = self.base.disabled(disabled);
264        self
265    }
266}
267
268impl Clickable for Button {
269    /// Sets the click event handler for the button.
270    fn on_click(
271        mut self,
272        handler: impl Fn(&gpui::ClickEvent, &mut WindowContext) + 'static,
273    ) -> Self {
274        self.base = self.base.on_click(handler);
275        self
276    }
277
278    fn cursor_style(mut self, cursor_style: gpui::CursorStyle) -> Self {
279        self.base = self.base.cursor_style(cursor_style);
280        self
281    }
282}
283
284impl FixedWidth for Button {
285    /// Sets a fixed width for the button.
286    ///
287    /// This function allows a button to have a fixed width instead of automatically growing or shrinking.
288    /// Sets a fixed width for the button.
289    ///
290    /// # Examples
291    ///
292    /// ```
293    /// use ui::prelude::*;
294    ///
295    /// Button::new("button_id", "Click me!")
296    ///     .width(px(100.).into())
297    ///     .on_click(|event, cx| {
298    ///         // Handle click event
299    ///     });
300    /// ```
301    ///
302    /// This sets the button's width to be exactly 100 pixels.
303    fn width(mut self, width: DefiniteLength) -> Self {
304        self.base = self.base.width(width);
305        self
306    }
307
308    /// Sets the button to occupy the full width of its container.
309    ///
310    /// # Examples
311    ///
312    /// ```
313    /// use ui::prelude::*;
314    ///
315    /// Button::new("button_id", "Click me!")
316    ///     .full_width()
317    ///     .on_click(|event, cx| {
318    ///         // Handle click event
319    ///     });
320    /// ```
321    ///
322    /// This stretches the button to the full width of its container.
323    fn full_width(mut self) -> Self {
324        self.base = self.base.full_width();
325        self
326    }
327}
328
329impl ButtonCommon for Button {
330    /// Sets the button's id.
331    fn id(&self) -> &ElementId {
332        self.base.id()
333    }
334
335    /// Sets the visual style of the button using a [`ButtonStyle`].
336    fn style(mut self, style: ButtonStyle) -> Self {
337        self.base = self.base.style(style);
338        self
339    }
340
341    /// Sets the button's size using a [`ButtonSize`].
342    fn size(mut self, size: ButtonSize) -> Self {
343        self.base = self.base.size(size);
344        self
345    }
346
347    /// Sets a tooltip for the button.
348    ///
349    /// This method allows a tooltip to be set for the button. The tooltip is a function that
350    /// takes a mutable reference to a [`WindowContext`] and returns an [`AnyView`]. The tooltip
351    /// is displayed when the user hovers over the button.
352    ///
353    /// # Examples
354    ///
355    /// ```
356    /// use ui::prelude::*;
357    /// use ui::Tooltip;
358    ///
359    /// Button::new("button_id", "Click me!")
360    ///     .tooltip(move |cx| {
361    ///         Tooltip::text("This is a tooltip", cx)
362    ///     })
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 WindowContext) -> 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, cx: &mut WindowContext) -> 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(Spacing::Small.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                            .selected(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(Spacing::Medium.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                            .selected(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}