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}