button.rs

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