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 WindowContext) + '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 reference to a [`WindowContext`] and returns an [`AnyView`]. The tooltip
353 /// 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(move |cx| {
363 /// Tooltip::text("This is a tooltip", cx)
364 /// })
365 /// .on_click(|event, cx| {
366 /// // Handle click event
367 /// });
368 /// ```
369 ///
370 /// This will create a button with a tooltip that displays "This is a tooltip" when hovered over.
371 fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
372 self.base = self.base.tooltip(tooltip);
373 self
374 }
375
376 fn layer(mut self, elevation: ElevationIndex) -> Self {
377 self.base = self.base.layer(elevation);
378 self
379 }
380}
381
382impl RenderOnce for Button {
383 #[allow(refining_impl_trait)]
384 fn render(self, cx: &mut WindowContext) -> ButtonLike {
385 let is_disabled = self.base.disabled;
386 let is_selected = self.base.selected;
387
388 let label = self
389 .selected_label
390 .filter(|_| is_selected)
391 .unwrap_or(self.label);
392
393 let label_color = if is_disabled {
394 Color::Disabled
395 } else if is_selected {
396 self.selected_label_color.unwrap_or(Color::Selected)
397 } else {
398 self.label_color.unwrap_or_default()
399 };
400
401 self.base.child(
402 h_flex()
403 .gap(DynamicSpacing::Base04.rems(cx))
404 .when(self.icon_position == Some(IconPosition::Start), |this| {
405 this.children(self.icon.map(|icon| {
406 ButtonIcon::new(icon)
407 .disabled(is_disabled)
408 .toggle_state(is_selected)
409 .selected_icon(self.selected_icon)
410 .selected_icon_color(self.selected_icon_color)
411 .size(self.icon_size)
412 .color(self.icon_color)
413 }))
414 })
415 .child(
416 h_flex()
417 .gap(DynamicSpacing::Base06.rems(cx))
418 .justify_between()
419 .child(
420 Label::new(label)
421 .color(label_color)
422 .size(self.label_size.unwrap_or_default())
423 .when_some(self.alpha, |this, alpha| this.alpha(alpha))
424 .line_height_style(LineHeightStyle::UiLabel),
425 )
426 .children(self.key_binding),
427 )
428 .when(self.icon_position != Some(IconPosition::Start), |this| {
429 this.children(self.icon.map(|icon| {
430 ButtonIcon::new(icon)
431 .disabled(is_disabled)
432 .toggle_state(is_selected)
433 .selected_icon(self.selected_icon)
434 .selected_icon_color(self.selected_icon_color)
435 .size(self.icon_size)
436 .color(self.icon_color)
437 }))
438 }),
439 )
440 }
441}
442
443impl ComponentPreview for Button {
444 fn description() -> impl Into<Option<&'static str>> {
445 "A button allows users to take actions, and make choices, with a single tap."
446 }
447
448 fn examples(_: &mut WindowContext) -> Vec<ComponentExampleGroup<Self>> {
449 vec![
450 example_group_with_title(
451 "Styles",
452 vec![
453 single_example("Default", Button::new("default", "Default")),
454 single_example(
455 "Filled",
456 Button::new("filled", "Filled").style(ButtonStyle::Filled),
457 ),
458 single_example(
459 "Subtle",
460 Button::new("outline", "Subtle").style(ButtonStyle::Subtle),
461 ),
462 single_example(
463 "Transparent",
464 Button::new("transparent", "Transparent").style(ButtonStyle::Transparent),
465 ),
466 ],
467 ),
468 example_group_with_title(
469 "Tinted",
470 vec![
471 single_example(
472 "Accent",
473 Button::new("tinted_accent", "Accent")
474 .style(ButtonStyle::Tinted(TintColor::Accent)),
475 ),
476 single_example(
477 "Error",
478 Button::new("tinted_negative", "Error")
479 .style(ButtonStyle::Tinted(TintColor::Error)),
480 ),
481 single_example(
482 "Warning",
483 Button::new("tinted_warning", "Warning")
484 .style(ButtonStyle::Tinted(TintColor::Warning)),
485 ),
486 single_example(
487 "Success",
488 Button::new("tinted_positive", "Success")
489 .style(ButtonStyle::Tinted(TintColor::Success)),
490 ),
491 ],
492 ),
493 example_group_with_title(
494 "States",
495 vec![
496 single_example("Default", Button::new("default_state", "Default")),
497 single_example(
498 "Disabled",
499 Button::new("disabled", "Disabled").disabled(true),
500 ),
501 single_example(
502 "Selected",
503 Button::new("selected", "Selected").toggle_state(true),
504 ),
505 ],
506 ),
507 example_group_with_title(
508 "With Icons",
509 vec![
510 single_example(
511 "Icon Start",
512 Button::new("icon_start", "Icon Start")
513 .icon(IconName::Check)
514 .icon_position(IconPosition::Start),
515 ),
516 single_example(
517 "Icon End",
518 Button::new("icon_end", "Icon End")
519 .icon(IconName::Check)
520 .icon_position(IconPosition::End),
521 ),
522 single_example(
523 "Icon Color",
524 Button::new("icon_color", "Icon Color")
525 .icon(IconName::Check)
526 .icon_color(Color::Accent),
527 ),
528 single_example(
529 "Tinted Icons",
530 Button::new("tinted_icons", "Error")
531 .style(ButtonStyle::Tinted(TintColor::Error))
532 .color(Color::Error)
533 .icon_color(Color::Error)
534 .icon(IconName::Trash)
535 .icon_position(IconPosition::Start),
536 ),
537 ],
538 ),
539 ]
540 }
541}