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
254impl FixedWidth for Button {
255 /// Sets a fixed width for the button.
256 ///
257 /// This function allows a button to have a fixed width instead of automatically growing or shrinking.
258 /// Sets a fixed width for the button.
259 ///
260 /// # Examples
261 ///
262 /// ```
263 /// use ui::prelude::*;
264 ///
265 /// Button::new("button_id", "Click me!")
266 /// .width(px(100.).into())
267 /// .on_click(|event, cx| {
268 /// // Handle click event
269 /// });
270 /// ```
271 ///
272 /// This sets the button's width to be exactly 100 pixels.
273 fn width(mut self, width: DefiniteLength) -> Self {
274 self.base = self.base.width(width);
275 self
276 }
277
278 /// Sets the button to occupy the full width of its container.
279 ///
280 /// # Examples
281 ///
282 /// ```
283 /// use ui::prelude::*;
284 ///
285 /// Button::new("button_id", "Click me!")
286 /// .full_width()
287 /// .on_click(|event, cx| {
288 /// // Handle click event
289 /// });
290 /// ```
291 ///
292 /// This stretches the button to the full width of its container.
293 fn full_width(mut self) -> Self {
294 self.base = self.base.full_width();
295 self
296 }
297}
298
299impl ButtonCommon for Button {
300 /// Sets the button's id.
301 fn id(&self) -> &ElementId {
302 self.base.id()
303 }
304
305 /// Sets the visual style of the button using a [`ButtonStyle`].
306 fn style(mut self, style: ButtonStyle) -> Self {
307 self.base = self.base.style(style);
308 self
309 }
310
311 /// Sets the button's size using a [`ButtonSize`].
312 fn size(mut self, size: ButtonSize) -> Self {
313 self.base = self.base.size(size);
314 self
315 }
316
317 /// Sets a tooltip for the button.
318 ///
319 /// This method allows a tooltip to be set for the button. The tooltip is a function that
320 /// takes a mutable reference to a [`WindowContext`] and returns an [`AnyView`]. The tooltip
321 /// is displayed when the user hovers over the button.
322 ///
323 /// # Examples
324 ///
325 /// ```
326 /// use ui::prelude::*;
327 /// use ui::Tooltip;
328 ///
329 /// Button::new("button_id", "Click me!")
330 /// .tooltip(move |cx| {
331 /// Tooltip::text("This is a tooltip", cx)
332 /// })
333 /// .on_click(|event, cx| {
334 /// // Handle click event
335 /// });
336 /// ```
337 ///
338 /// This will create a button with a tooltip that displays "This is a tooltip" when hovered over.
339 fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
340 self.base = self.base.tooltip(tooltip);
341 self
342 }
343
344 fn layer(mut self, elevation: ElevationIndex) -> Self {
345 self.base = self.base.layer(elevation);
346 self
347 }
348}
349
350impl RenderOnce for Button {
351 #[allow(refining_impl_trait)]
352 fn render(self, cx: &mut WindowContext) -> ButtonLike {
353 let is_disabled = self.base.disabled;
354 let is_selected = self.base.selected;
355
356 let label = self
357 .selected_label
358 .filter(|_| is_selected)
359 .unwrap_or(self.label);
360
361 let label_color = if is_disabled {
362 Color::Disabled
363 } else if is_selected {
364 Color::Selected
365 } else {
366 self.label_color.unwrap_or_default()
367 };
368
369 self.base.child(
370 h_flex()
371 .gap(Spacing::Small.rems(cx))
372 .when(self.icon_position == Some(IconPosition::Start), |this| {
373 this.children(self.icon.map(|icon| {
374 ButtonIcon::new(icon)
375 .disabled(is_disabled)
376 .selected(is_selected)
377 .selected_icon(self.selected_icon)
378 .size(self.icon_size)
379 .color(self.icon_color)
380 }))
381 })
382 .child(
383 h_flex()
384 .gap(Spacing::Medium.rems(cx))
385 .justify_between()
386 .child(
387 Label::new(label)
388 .color(label_color)
389 .size(self.label_size.unwrap_or_default())
390 .line_height_style(LineHeightStyle::UiLabel),
391 )
392 .children(self.key_binding),
393 )
394 .when(self.icon_position != Some(IconPosition::Start), |this| {
395 this.children(self.icon.map(|icon| {
396 ButtonIcon::new(icon)
397 .disabled(is_disabled)
398 .selected(is_selected)
399 .selected_icon(self.selected_icon)
400 .size(self.icon_size)
401 .color(self.icon_color)
402 }))
403 }),
404 )
405 }
406}