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