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}