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 icon: Option<IconName>,
85 icon_position: Option<IconPosition>,
86 icon_size: Option<IconSize>,
87 icon_color: Option<Color>,
88 selected_icon: Option<IconName>,
89 key_binding: Option<KeyBinding>,
90}
91
92impl Button {
93 /// Creates a new [`Button`] with a specified identifier and label.
94 ///
95 /// This is the primary constructor for a [`Button`] component. It initializes
96 /// the button with the provided identifier and label text, setting all other
97 /// properties to their default values, which can be customized using the
98 /// builder pattern methods provided by this struct.
99 pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
100 Self {
101 base: ButtonLike::new(id),
102 label: label.into(),
103 label_color: None,
104 label_size: None,
105 selected_label: None,
106 icon: None,
107 icon_position: None,
108 icon_size: None,
109 icon_color: None,
110 selected_icon: None,
111 key_binding: None,
112 }
113 }
114
115 /// Sets the color of the button's label.
116 pub fn color(mut self, label_color: impl Into<Option<Color>>) -> Self {
117 self.label_color = label_color.into();
118 self
119 }
120
121 /// Defines the size of the button's label.
122 pub fn label_size(mut self, label_size: impl Into<Option<LabelSize>>) -> Self {
123 self.label_size = label_size.into();
124 self
125 }
126
127 /// Sets the label used when the button is in a selected state.
128 pub fn selected_label<L: Into<SharedString>>(mut self, label: impl Into<Option<L>>) -> Self {
129 self.selected_label = label.into().map(Into::into);
130 self
131 }
132
133 /// Assigns an icon to the button.
134 pub fn icon(mut self, icon: impl Into<Option<IconName>>) -> Self {
135 self.icon = icon.into();
136 self
137 }
138
139 /// Sets the position of the icon relative to the label.
140 pub fn icon_position(mut self, icon_position: impl Into<Option<IconPosition>>) -> Self {
141 self.icon_position = icon_position.into();
142 self
143 }
144
145 /// Specifies the size of the button's icon.
146 pub fn icon_size(mut self, icon_size: impl Into<Option<IconSize>>) -> Self {
147 self.icon_size = icon_size.into();
148 self
149 }
150
151 /// Sets the color of the button's icon.
152 pub fn icon_color(mut self, icon_color: impl Into<Option<Color>>) -> Self {
153 self.icon_color = icon_color.into();
154 self
155 }
156
157 /// Chooses an icon to display when the button is in a selected state.
158 pub fn selected_icon(mut self, icon: impl Into<Option<IconName>>) -> Self {
159 self.selected_icon = icon.into();
160 self
161 }
162
163 /// Binds a key combination to the button for keyboard shortcuts.
164 pub fn key_binding(mut self, key_binding: impl Into<Option<KeyBinding>>) -> Self {
165 self.key_binding = key_binding.into();
166 self
167 }
168}
169
170impl Selectable for Button {
171 /// Sets the selected state of the button.
172 ///
173 /// This method allows the selection state of the button to be specified.
174 /// It modifies the button's appearance to reflect its selected state.
175 ///
176 /// # Examples
177 ///
178 /// ```
179 /// use ui::prelude::*;
180 ///
181 /// Button::new("button_id", "Click me!")
182 /// .selected(true)
183 /// .on_click(|event, cx| {
184 /// // Handle click event
185 /// });
186 /// ```
187 ///
188 /// Use [`selected_style`](Button::selected_style) to change the style of the button when it is selected.
189 fn selected(mut self, selected: bool) -> Self {
190 self.base = self.base.selected(selected);
191 self
192 }
193}
194
195impl SelectableButton for Button {
196 /// Sets the style for the button when selected.
197 ///
198 /// # Examples
199 ///
200 /// ```
201 /// use ui::prelude::*;
202 /// use ui::TintColor;
203 ///
204 /// Button::new("button_id", "Click me!")
205 /// .selected(true)
206 /// .selected_style(ButtonStyle::Tinted(TintColor::Accent))
207 /// .on_click(|event, cx| {
208 /// // Handle click event
209 /// });
210 /// ```
211 /// This results in a button with a blue tinted background when selected.
212 fn selected_style(mut self, style: ButtonStyle) -> Self {
213 self.base = self.base.selected_style(style);
214 self
215 }
216}
217
218impl Disableable for Button {
219 /// Disables the button.
220 ///
221 /// This method allows the button to be disabled. When a button is disabled,
222 /// it doesn't react to user interactions and its appearance is updated to reflect this.
223 ///
224 /// # Examples
225 ///
226 /// ```
227 /// use ui::prelude::*;
228 ///
229 /// Button::new("button_id", "Click me!")
230 /// .disabled(true)
231 /// .on_click(|event, cx| {
232 /// // Handle click event
233 /// });
234 /// ```
235 ///
236 /// This results in a button that is disabled and does not respond to click events.
237 fn disabled(mut self, disabled: bool) -> Self {
238 self.base = self.base.disabled(disabled);
239 self
240 }
241}
242
243impl Clickable for Button {
244 /// Sets the click event handler for the button.
245 fn on_click(
246 mut self,
247 handler: impl Fn(&gpui::ClickEvent, &mut WindowContext) + 'static,
248 ) -> Self {
249 self.base = self.base.on_click(handler);
250 self
251 }
252
253 fn cursor_style(mut self, cursor_style: gpui::CursorStyle) -> Self {
254 self.base = self.base.cursor_style(cursor_style);
255 self
256 }
257}
258
259impl FixedWidth for Button {
260 /// Sets a fixed width for the button.
261 ///
262 /// This function allows a button to have a fixed width instead of automatically growing or shrinking.
263 /// Sets a fixed width for the button.
264 ///
265 /// # Examples
266 ///
267 /// ```
268 /// use ui::prelude::*;
269 ///
270 /// Button::new("button_id", "Click me!")
271 /// .width(px(100.).into())
272 /// .on_click(|event, cx| {
273 /// // Handle click event
274 /// });
275 /// ```
276 ///
277 /// This sets the button's width to be exactly 100 pixels.
278 fn width(mut self, width: DefiniteLength) -> Self {
279 self.base = self.base.width(width);
280 self
281 }
282
283 /// Sets the button to occupy the full width of its container.
284 ///
285 /// # Examples
286 ///
287 /// ```
288 /// use ui::prelude::*;
289 ///
290 /// Button::new("button_id", "Click me!")
291 /// .full_width()
292 /// .on_click(|event, cx| {
293 /// // Handle click event
294 /// });
295 /// ```
296 ///
297 /// This stretches the button to the full width of its container.
298 fn full_width(mut self) -> Self {
299 self.base = self.base.full_width();
300 self
301 }
302}
303
304impl ButtonCommon for Button {
305 /// Sets the button's id.
306 fn id(&self) -> &ElementId {
307 self.base.id()
308 }
309
310 /// Sets the visual style of the button using a [`ButtonStyle`].
311 fn style(mut self, style: ButtonStyle) -> Self {
312 self.base = self.base.style(style);
313 self
314 }
315
316 /// Sets the button's size using a [`ButtonSize`].
317 fn size(mut self, size: ButtonSize) -> Self {
318 self.base = self.base.size(size);
319 self
320 }
321
322 /// Sets a tooltip for the button.
323 ///
324 /// This method allows a tooltip to be set for the button. The tooltip is a function that
325 /// takes a mutable reference to a [`WindowContext`] and returns an [`AnyView`]. The tooltip
326 /// is displayed when the user hovers over the button.
327 ///
328 /// # Examples
329 ///
330 /// ```
331 /// use ui::prelude::*;
332 /// use ui::Tooltip;
333 ///
334 /// Button::new("button_id", "Click me!")
335 /// .tooltip(move |cx| {
336 /// Tooltip::text("This is a tooltip", cx)
337 /// })
338 /// .on_click(|event, cx| {
339 /// // Handle click event
340 /// });
341 /// ```
342 ///
343 /// This will create a button with a tooltip that displays "This is a tooltip" when hovered over.
344 fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
345 self.base = self.base.tooltip(tooltip);
346 self
347 }
348
349 fn layer(mut self, elevation: ElevationIndex) -> Self {
350 self.base = self.base.layer(elevation);
351 self
352 }
353}
354
355impl RenderOnce for Button {
356 #[allow(refining_impl_trait)]
357 fn render(self, cx: &mut WindowContext) -> ButtonLike {
358 let is_disabled = self.base.disabled;
359 let is_selected = self.base.selected;
360
361 let label = self
362 .selected_label
363 .filter(|_| is_selected)
364 .unwrap_or(self.label);
365
366 let label_color = if is_disabled {
367 Color::Disabled
368 } else if is_selected {
369 Color::Selected
370 } else {
371 self.label_color.unwrap_or_default()
372 };
373
374 self.base.child(
375 h_flex()
376 .gap(Spacing::Small.rems(cx))
377 .when(self.icon_position == Some(IconPosition::Start), |this| {
378 this.children(self.icon.map(|icon| {
379 ButtonIcon::new(icon)
380 .disabled(is_disabled)
381 .selected(is_selected)
382 .selected_icon(self.selected_icon)
383 .size(self.icon_size)
384 .color(self.icon_color)
385 }))
386 })
387 .child(
388 h_flex()
389 .gap(Spacing::Medium.rems(cx))
390 .justify_between()
391 .child(
392 Label::new(label)
393 .color(label_color)
394 .size(self.label_size.unwrap_or_default())
395 .line_height_style(LineHeightStyle::UiLabel),
396 )
397 .children(self.key_binding),
398 )
399 .when(self.icon_position != Some(IconPosition::Start), |this| {
400 this.children(self.icon.map(|icon| {
401 ButtonIcon::new(icon)
402 .disabled(is_disabled)
403 .selected(is_selected)
404 .selected_icon(self.selected_icon)
405 .size(self.icon_size)
406 .color(self.icon_color)
407 }))
408 }),
409 )
410 }
411}