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