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