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