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