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