1use documented::Documented;
2use gpui::{
3 AnyElement, AnyView, ClickEvent, CursorStyle, DefiniteLength, FocusHandle, Hsla, MouseButton,
4 MouseClickEvent, MouseDownEvent, MouseUpEvent, Rems, StyleRefinement, relative,
5 transparent_black,
6};
7use smallvec::SmallVec;
8
9use crate::{DynamicSpacing, ElevationIndex, prelude::*};
10
11/// A trait for buttons that can be Selected. Enables setting the [`ButtonStyle`] of a button when it is selected.
12pub trait SelectableButton: Toggleable {
13 fn selected_style(self, style: ButtonStyle) -> Self;
14}
15
16/// A common set of traits all buttons must implement.
17pub trait ButtonCommon: Clickable + Disableable {
18 /// A unique element ID to identify the button.
19 fn id(&self) -> &ElementId;
20
21 /// The visual style of the button.
22 ///
23 /// Most commonly will be [`ButtonStyle::Subtle`], or [`ButtonStyle::Filled`]
24 /// for an emphasized button.
25 fn style(self, style: ButtonStyle) -> Self;
26
27 /// The size of the button.
28 ///
29 /// Most buttons will use the default size.
30 ///
31 /// [`ButtonSize`] can also be used to help build non-button elements
32 /// that are consistently sized with buttons.
33 fn size(self, size: ButtonSize) -> Self;
34
35 /// The tooltip that shows when a user hovers over the button.
36 ///
37 /// Nearly all interactable elements should have a tooltip. Some example
38 /// exceptions might a scroll bar, or a slider.
39 fn tooltip(self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self;
40
41 fn tab_index(self, tab_index: impl Into<isize>) -> Self;
42
43 fn layer(self, elevation: ElevationIndex) -> Self;
44
45 fn track_focus(self, focus_handle: &FocusHandle) -> Self;
46}
47
48#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
49pub enum IconPosition {
50 #[default]
51 Start,
52 End,
53}
54
55#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
56pub enum KeybindingPosition {
57 Start,
58 #[default]
59 End,
60}
61
62#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
63pub enum TintColor {
64 #[default]
65 Accent,
66 Error,
67 Warning,
68 Success,
69}
70
71impl TintColor {
72 fn button_like_style(self, cx: &mut App) -> ButtonLikeStyles {
73 match self {
74 TintColor::Accent => ButtonLikeStyles {
75 background: cx.theme().status().info_background,
76 border_color: cx.theme().status().info_border,
77 label_color: cx.theme().colors().text,
78 icon_color: cx.theme().colors().text,
79 },
80 TintColor::Error => ButtonLikeStyles {
81 background: cx.theme().status().error_background,
82 border_color: cx.theme().status().error_border,
83 label_color: cx.theme().colors().text,
84 icon_color: cx.theme().colors().text,
85 },
86 TintColor::Warning => ButtonLikeStyles {
87 background: cx.theme().status().warning_background,
88 border_color: cx.theme().status().warning_border,
89 label_color: cx.theme().colors().text,
90 icon_color: cx.theme().colors().text,
91 },
92 TintColor::Success => ButtonLikeStyles {
93 background: cx.theme().status().success_background,
94 border_color: cx.theme().status().success_border,
95 label_color: cx.theme().colors().text,
96 icon_color: cx.theme().colors().text,
97 },
98 }
99 }
100}
101
102impl From<TintColor> for Color {
103 fn from(tint: TintColor) -> Self {
104 match tint {
105 TintColor::Accent => Color::Accent,
106 TintColor::Error => Color::Error,
107 TintColor::Warning => Color::Warning,
108 TintColor::Success => Color::Success,
109 }
110 }
111}
112
113// Used to go from ButtonStyle -> Color through tint colors.
114impl From<ButtonStyle> for Color {
115 fn from(style: ButtonStyle) -> Self {
116 match style {
117 ButtonStyle::Tinted(tint) => tint.into(),
118 _ => Color::Default,
119 }
120 }
121}
122
123/// The visual appearance of a button.
124#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
125pub enum ButtonStyle {
126 /// A filled button with a solid background color. Provides emphasis versus
127 /// the more common subtle button.
128 Filled,
129
130 /// Used to emphasize a button in some way, like a selected state, or a semantic
131 /// coloring like an error or success button.
132 Tinted(TintColor),
133
134 /// Usually used as a secondary action that should have more emphasis than
135 /// a fully transparent button.
136 Outlined,
137
138 /// A more de-emphasized version of the outlined button.
139 OutlinedGhost,
140
141 /// The default button style, used for most buttons. Has a transparent background,
142 /// but has a background color to indicate states like hover and active.
143 #[default]
144 Subtle,
145
146 /// Used for buttons that only change foreground color on hover and active states.
147 ///
148 /// TODO: Better docs for this.
149 Transparent,
150}
151
152#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
153pub(crate) enum ButtonLikeRounding {
154 All,
155 Left,
156 Right,
157}
158
159#[derive(Debug, Clone)]
160pub(crate) struct ButtonLikeStyles {
161 pub background: Hsla,
162 #[allow(unused)]
163 pub border_color: Hsla,
164 #[allow(unused)]
165 pub label_color: Hsla,
166 #[allow(unused)]
167 pub icon_color: Hsla,
168}
169
170fn element_bg_from_elevation(elevation: Option<ElevationIndex>, cx: &mut App) -> Hsla {
171 match elevation {
172 Some(ElevationIndex::Background) => cx.theme().colors().element_background,
173 Some(ElevationIndex::ElevatedSurface) => cx.theme().colors().elevated_surface_background,
174 Some(ElevationIndex::Surface) => cx.theme().colors().surface_background,
175 Some(ElevationIndex::ModalSurface) => cx.theme().colors().background,
176 _ => cx.theme().colors().element_background,
177 }
178}
179
180impl ButtonStyle {
181 pub(crate) fn enabled(
182 self,
183 elevation: Option<ElevationIndex>,
184
185 cx: &mut App,
186 ) -> ButtonLikeStyles {
187 match self {
188 ButtonStyle::Filled => ButtonLikeStyles {
189 background: element_bg_from_elevation(elevation, cx),
190 border_color: transparent_black(),
191 label_color: Color::Default.color(cx),
192 icon_color: Color::Default.color(cx),
193 },
194 ButtonStyle::Tinted(tint) => tint.button_like_style(cx),
195 ButtonStyle::Outlined => ButtonLikeStyles {
196 background: element_bg_from_elevation(elevation, cx),
197 border_color: cx.theme().colors().border_variant,
198 label_color: Color::Default.color(cx),
199 icon_color: Color::Default.color(cx),
200 },
201 ButtonStyle::OutlinedGhost => ButtonLikeStyles {
202 background: transparent_black(),
203 border_color: cx.theme().colors().border_variant,
204 label_color: Color::Default.color(cx),
205 icon_color: Color::Default.color(cx),
206 },
207 ButtonStyle::Subtle => ButtonLikeStyles {
208 background: cx.theme().colors().ghost_element_background,
209 border_color: transparent_black(),
210 label_color: Color::Default.color(cx),
211 icon_color: Color::Default.color(cx),
212 },
213 ButtonStyle::Transparent => ButtonLikeStyles {
214 background: transparent_black(),
215 border_color: transparent_black(),
216 label_color: Color::Default.color(cx),
217 icon_color: Color::Default.color(cx),
218 },
219 }
220 }
221
222 pub(crate) fn hovered(
223 self,
224 elevation: Option<ElevationIndex>,
225
226 cx: &mut App,
227 ) -> ButtonLikeStyles {
228 match self {
229 ButtonStyle::Filled => {
230 let mut filled_background = element_bg_from_elevation(elevation, cx);
231 filled_background.fade_out(0.5);
232
233 ButtonLikeStyles {
234 background: filled_background,
235 border_color: transparent_black(),
236 label_color: Color::Default.color(cx),
237 icon_color: Color::Default.color(cx),
238 }
239 }
240 ButtonStyle::Tinted(tint) => {
241 let mut styles = tint.button_like_style(cx);
242 let theme = cx.theme();
243 styles.background = theme.darken(styles.background, 0.05, 0.2);
244 styles
245 }
246 ButtonStyle::Outlined => ButtonLikeStyles {
247 background: cx.theme().colors().ghost_element_hover,
248 border_color: cx.theme().colors().border,
249 label_color: Color::Default.color(cx),
250 icon_color: Color::Default.color(cx),
251 },
252 ButtonStyle::OutlinedGhost => ButtonLikeStyles {
253 background: transparent_black(),
254 border_color: cx.theme().colors().border,
255 label_color: Color::Default.color(cx),
256 icon_color: Color::Default.color(cx),
257 },
258 ButtonStyle::Subtle => ButtonLikeStyles {
259 background: cx.theme().colors().ghost_element_hover,
260 border_color: transparent_black(),
261 label_color: Color::Default.color(cx),
262 icon_color: Color::Default.color(cx),
263 },
264 ButtonStyle::Transparent => ButtonLikeStyles {
265 background: transparent_black(),
266 border_color: transparent_black(),
267 // TODO: These are not great
268 label_color: Color::Muted.color(cx),
269 // TODO: These are not great
270 icon_color: Color::Muted.color(cx),
271 },
272 }
273 }
274
275 pub(crate) fn active(self, cx: &mut App) -> ButtonLikeStyles {
276 match self {
277 ButtonStyle::Filled => ButtonLikeStyles {
278 background: cx.theme().colors().element_active,
279 border_color: transparent_black(),
280 label_color: Color::Default.color(cx),
281 icon_color: Color::Default.color(cx),
282 },
283 ButtonStyle::Tinted(tint) => tint.button_like_style(cx),
284 ButtonStyle::Subtle => ButtonLikeStyles {
285 background: cx.theme().colors().ghost_element_active,
286 border_color: transparent_black(),
287 label_color: Color::Default.color(cx),
288 icon_color: Color::Default.color(cx),
289 },
290 ButtonStyle::Outlined => ButtonLikeStyles {
291 background: cx.theme().colors().element_active,
292 border_color: cx.theme().colors().border_variant,
293 label_color: Color::Default.color(cx),
294 icon_color: Color::Default.color(cx),
295 },
296 ButtonStyle::OutlinedGhost => ButtonLikeStyles {
297 background: transparent_black(),
298 border_color: cx.theme().colors().border_variant,
299 label_color: Color::Default.color(cx),
300 icon_color: Color::Default.color(cx),
301 },
302 ButtonStyle::Transparent => ButtonLikeStyles {
303 background: transparent_black(),
304 border_color: transparent_black(),
305 // TODO: These are not great
306 label_color: Color::Muted.color(cx),
307 // TODO: These are not great
308 icon_color: Color::Muted.color(cx),
309 },
310 }
311 }
312
313 #[allow(unused)]
314 pub(crate) fn focused(self, window: &mut Window, cx: &mut App) -> ButtonLikeStyles {
315 match self {
316 ButtonStyle::Filled => ButtonLikeStyles {
317 background: cx.theme().colors().element_background,
318 border_color: cx.theme().colors().border_focused,
319 label_color: Color::Default.color(cx),
320 icon_color: Color::Default.color(cx),
321 },
322 ButtonStyle::Tinted(tint) => tint.button_like_style(cx),
323 ButtonStyle::Subtle => ButtonLikeStyles {
324 background: cx.theme().colors().ghost_element_background,
325 border_color: cx.theme().colors().border_focused,
326 label_color: Color::Default.color(cx),
327 icon_color: Color::Default.color(cx),
328 },
329 ButtonStyle::Outlined => ButtonLikeStyles {
330 background: cx.theme().colors().ghost_element_background,
331 border_color: cx.theme().colors().border,
332 label_color: Color::Default.color(cx),
333 icon_color: Color::Default.color(cx),
334 },
335 ButtonStyle::OutlinedGhost => ButtonLikeStyles {
336 background: transparent_black(),
337 border_color: cx.theme().colors().border,
338 label_color: Color::Default.color(cx),
339 icon_color: Color::Default.color(cx),
340 },
341 ButtonStyle::Transparent => ButtonLikeStyles {
342 background: transparent_black(),
343 border_color: cx.theme().colors().border_focused,
344 label_color: Color::Accent.color(cx),
345 icon_color: Color::Accent.color(cx),
346 },
347 }
348 }
349
350 #[allow(unused)]
351 pub(crate) fn disabled(
352 self,
353 elevation: Option<ElevationIndex>,
354 window: &mut Window,
355 cx: &mut App,
356 ) -> ButtonLikeStyles {
357 match self {
358 ButtonStyle::Filled => ButtonLikeStyles {
359 background: cx.theme().colors().element_disabled,
360 border_color: cx.theme().colors().border_disabled,
361 label_color: Color::Disabled.color(cx),
362 icon_color: Color::Disabled.color(cx),
363 },
364 ButtonStyle::Tinted(tint) => tint.button_like_style(cx),
365 ButtonStyle::Subtle => ButtonLikeStyles {
366 background: cx.theme().colors().ghost_element_disabled,
367 border_color: cx.theme().colors().border_disabled,
368 label_color: Color::Disabled.color(cx),
369 icon_color: Color::Disabled.color(cx),
370 },
371 ButtonStyle::Outlined => ButtonLikeStyles {
372 background: cx.theme().colors().element_disabled,
373 border_color: cx.theme().colors().border_disabled,
374 label_color: Color::Default.color(cx),
375 icon_color: Color::Default.color(cx),
376 },
377 ButtonStyle::OutlinedGhost => ButtonLikeStyles {
378 background: transparent_black(),
379 border_color: cx.theme().colors().border_disabled,
380 label_color: Color::Default.color(cx),
381 icon_color: Color::Default.color(cx),
382 },
383 ButtonStyle::Transparent => ButtonLikeStyles {
384 background: transparent_black(),
385 border_color: transparent_black(),
386 label_color: Color::Disabled.color(cx),
387 icon_color: Color::Disabled.color(cx),
388 },
389 }
390 }
391}
392
393/// The height of a button.
394///
395/// Can also be used to size non-button elements to align with [`Button`]s.
396#[derive(Default, PartialEq, Clone, Copy)]
397pub enum ButtonSize {
398 Large,
399 Medium,
400 #[default]
401 Default,
402 Compact,
403 None,
404}
405
406impl ButtonSize {
407 pub fn rems(self) -> Rems {
408 match self {
409 ButtonSize::Large => rems_from_px(32.),
410 ButtonSize::Medium => rems_from_px(28.),
411 ButtonSize::Default => rems_from_px(22.),
412 ButtonSize::Compact => rems_from_px(18.),
413 ButtonSize::None => rems_from_px(16.),
414 }
415 }
416}
417
418/// A button-like element that can be used to create a custom button when
419/// prebuilt buttons are not sufficient. Use this sparingly, as it is
420/// unconstrained and may make the UI feel less consistent.
421///
422/// This is also used to build the prebuilt buttons.
423#[derive(IntoElement, Documented, RegisterComponent)]
424pub struct ButtonLike {
425 pub(super) base: Div,
426 id: ElementId,
427 pub(super) style: ButtonStyle,
428 pub(super) disabled: bool,
429 pub(super) selected: bool,
430 pub(super) selected_style: Option<ButtonStyle>,
431 pub(super) width: Option<DefiniteLength>,
432 pub(super) height: Option<DefiniteLength>,
433 pub(super) layer: Option<ElevationIndex>,
434 tab_index: Option<isize>,
435 size: ButtonSize,
436 rounding: Option<ButtonLikeRounding>,
437 tooltip: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyView>>,
438 hoverable_tooltip: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyView>>,
439 cursor_style: CursorStyle,
440 on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
441 on_right_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
442 children: SmallVec<[AnyElement; 2]>,
443 focus_handle: Option<FocusHandle>,
444}
445
446impl ButtonLike {
447 pub fn new(id: impl Into<ElementId>) -> Self {
448 Self {
449 base: div(),
450 id: id.into(),
451 style: ButtonStyle::default(),
452 disabled: false,
453 selected: false,
454 selected_style: None,
455 width: None,
456 height: None,
457 size: ButtonSize::Default,
458 rounding: Some(ButtonLikeRounding::All),
459 tooltip: None,
460 hoverable_tooltip: None,
461 children: SmallVec::new(),
462 cursor_style: CursorStyle::PointingHand,
463 on_click: None,
464 on_right_click: None,
465 layer: None,
466 tab_index: None,
467 focus_handle: None,
468 }
469 }
470
471 pub fn new_rounded_left(id: impl Into<ElementId>) -> Self {
472 Self::new(id).rounding(ButtonLikeRounding::Left)
473 }
474
475 pub fn new_rounded_right(id: impl Into<ElementId>) -> Self {
476 Self::new(id).rounding(ButtonLikeRounding::Right)
477 }
478
479 pub fn new_rounded_all(id: impl Into<ElementId>) -> Self {
480 Self::new(id).rounding(ButtonLikeRounding::All)
481 }
482
483 pub fn opacity(mut self, opacity: f32) -> Self {
484 self.base = self.base.opacity(opacity);
485 self
486 }
487
488 pub fn height(mut self, height: DefiniteLength) -> Self {
489 self.height = Some(height);
490 self
491 }
492
493 pub(crate) fn rounding(mut self, rounding: impl Into<Option<ButtonLikeRounding>>) -> Self {
494 self.rounding = rounding.into();
495 self
496 }
497
498 pub fn on_right_click(
499 mut self,
500 handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
501 ) -> Self {
502 self.on_right_click = Some(Box::new(handler));
503 self
504 }
505
506 pub fn hoverable_tooltip(
507 mut self,
508 tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
509 ) -> Self {
510 self.hoverable_tooltip = Some(Box::new(tooltip));
511 self
512 }
513}
514
515impl Disableable for ButtonLike {
516 fn disabled(mut self, disabled: bool) -> Self {
517 self.disabled = disabled;
518 self
519 }
520}
521
522impl Toggleable for ButtonLike {
523 fn toggle_state(mut self, selected: bool) -> Self {
524 self.selected = selected;
525 self
526 }
527}
528
529impl SelectableButton for ButtonLike {
530 fn selected_style(mut self, style: ButtonStyle) -> Self {
531 self.selected_style = Some(style);
532 self
533 }
534}
535
536impl Clickable for ButtonLike {
537 fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self {
538 self.on_click = Some(Box::new(handler));
539 self
540 }
541
542 fn cursor_style(mut self, cursor_style: CursorStyle) -> Self {
543 self.cursor_style = cursor_style;
544 self
545 }
546}
547
548impl FixedWidth for ButtonLike {
549 fn width(mut self, width: impl Into<DefiniteLength>) -> Self {
550 self.width = Some(width.into());
551 self
552 }
553
554 fn full_width(mut self) -> Self {
555 self.width = Some(relative(1.));
556 self
557 }
558}
559
560impl ButtonCommon for ButtonLike {
561 fn id(&self) -> &ElementId {
562 &self.id
563 }
564
565 fn style(mut self, style: ButtonStyle) -> Self {
566 self.style = style;
567 self
568 }
569
570 fn size(mut self, size: ButtonSize) -> Self {
571 self.size = size;
572 self
573 }
574
575 fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
576 self.tooltip = Some(Box::new(tooltip));
577 self
578 }
579
580 fn tab_index(mut self, tab_index: impl Into<isize>) -> Self {
581 self.tab_index = Some(tab_index.into());
582 self
583 }
584
585 fn layer(mut self, elevation: ElevationIndex) -> Self {
586 self.layer = Some(elevation);
587 self
588 }
589
590 fn track_focus(mut self, focus_handle: &gpui::FocusHandle) -> Self {
591 self.focus_handle = Some(focus_handle.clone());
592 self
593 }
594}
595
596impl VisibleOnHover for ButtonLike {
597 fn visible_on_hover(mut self, group_name: impl Into<SharedString>) -> Self {
598 self.base = self.base.visible_on_hover(group_name);
599 self
600 }
601}
602
603impl ParentElement for ButtonLike {
604 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
605 self.children.extend(elements)
606 }
607}
608
609impl RenderOnce for ButtonLike {
610 fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
611 let style = self
612 .selected_style
613 .filter(|_| self.selected)
614 .unwrap_or(self.style);
615
616 self.base
617 .h_flex()
618 .id(self.id.clone())
619 .when_some(self.tab_index, |this, tab_index| this.tab_index(tab_index))
620 .when_some(self.focus_handle, |this, focus_handle| {
621 this.track_focus(&focus_handle)
622 })
623 .font_ui(cx)
624 .group("")
625 .flex_none()
626 .h(self.height.unwrap_or(self.size.rems().into()))
627 .when_some(self.width, |this, width| {
628 this.w(width).justify_center().text_center()
629 })
630 .when(
631 matches!(
632 self.style,
633 ButtonStyle::Outlined | ButtonStyle::OutlinedGhost
634 ),
635 |this| this.border_1(),
636 )
637 .when_some(self.rounding, |this, rounding| match rounding {
638 ButtonLikeRounding::All => this.rounded_sm(),
639 ButtonLikeRounding::Left => this.rounded_l_sm(),
640 ButtonLikeRounding::Right => this.rounded_r_sm(),
641 })
642 .gap(DynamicSpacing::Base04.rems(cx))
643 .map(|this| match self.size {
644 ButtonSize::Large | ButtonSize::Medium => this.px(DynamicSpacing::Base06.rems(cx)),
645 ButtonSize::Default | ButtonSize::Compact => {
646 this.px(DynamicSpacing::Base04.rems(cx))
647 }
648 ButtonSize::None => this.px_px(),
649 })
650 .border_color(style.enabled(self.layer, cx).border_color)
651 .bg(style.enabled(self.layer, cx).background)
652 .when(self.disabled, |this| {
653 if self.cursor_style == CursorStyle::PointingHand {
654 this.cursor_not_allowed()
655 } else {
656 this.cursor(self.cursor_style)
657 }
658 })
659 .when(!self.disabled, |this| {
660 let hovered_style = style.hovered(self.layer, cx);
661 let focus_color =
662 |refinement: StyleRefinement| refinement.bg(hovered_style.background);
663 this.cursor(self.cursor_style)
664 .hover(focus_color)
665 .map(|this| {
666 if matches!(self.style, ButtonStyle::Outlined) {
667 this.focus(|s| s.border_color(cx.theme().colors().border_focused))
668 } else {
669 this.focus(focus_color)
670 }
671 })
672 .active(|active| active.bg(style.active(cx).background))
673 })
674 .when_some(
675 self.on_right_click.filter(|_| !self.disabled),
676 |this, on_right_click| {
677 this.on_mouse_down(MouseButton::Right, |_event, window, cx| {
678 window.prevent_default();
679 cx.stop_propagation();
680 })
681 .on_mouse_up(
682 MouseButton::Right,
683 move |event, window, cx| {
684 cx.stop_propagation();
685 let click_event = ClickEvent::Mouse(MouseClickEvent {
686 down: MouseDownEvent {
687 button: MouseButton::Right,
688 position: event.position,
689 modifiers: event.modifiers,
690 click_count: 1,
691 first_mouse: false,
692 },
693 up: MouseUpEvent {
694 button: MouseButton::Right,
695 position: event.position,
696 modifiers: event.modifiers,
697 click_count: 1,
698 },
699 });
700 (on_right_click)(&click_event, window, cx)
701 },
702 )
703 },
704 )
705 .when_some(
706 self.on_click.filter(|_| !self.disabled),
707 |this, on_click| {
708 this.on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
709 .on_click(move |event, window, cx| {
710 cx.stop_propagation();
711 (on_click)(event, window, cx)
712 })
713 },
714 )
715 .when_some(self.tooltip, |this, tooltip| {
716 this.tooltip(move |window, cx| tooltip(window, cx))
717 })
718 .when_some(self.hoverable_tooltip, |this, tooltip| {
719 this.hoverable_tooltip(move |window, cx| tooltip(window, cx))
720 })
721 .children(self.children)
722 }
723}
724
725impl Component for ButtonLike {
726 fn scope() -> ComponentScope {
727 ComponentScope::Input
728 }
729
730 fn sort_name() -> &'static str {
731 // ButtonLike should be at the bottom of the button list
732 "ButtonZ"
733 }
734
735 fn description() -> Option<&'static str> {
736 Some(ButtonLike::DOCS)
737 }
738
739 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
740 Some(
741 v_flex()
742 .gap_6()
743 .children(vec![
744 example_group(vec![
745 single_example(
746 "Default",
747 ButtonLike::new("default")
748 .child(Label::new("Default"))
749 .into_any_element(),
750 ),
751 single_example(
752 "Filled",
753 ButtonLike::new("filled")
754 .style(ButtonStyle::Filled)
755 .child(Label::new("Filled"))
756 .into_any_element(),
757 ),
758 single_example(
759 "Subtle",
760 ButtonLike::new("outline")
761 .style(ButtonStyle::Subtle)
762 .child(Label::new("Subtle"))
763 .into_any_element(),
764 ),
765 single_example(
766 "Tinted",
767 ButtonLike::new("tinted_accent_style")
768 .style(ButtonStyle::Tinted(TintColor::Accent))
769 .child(Label::new("Accent"))
770 .into_any_element(),
771 ),
772 single_example(
773 "Transparent",
774 ButtonLike::new("transparent")
775 .style(ButtonStyle::Transparent)
776 .child(Label::new("Transparent"))
777 .into_any_element(),
778 ),
779 ]),
780 example_group_with_title(
781 "Button Group Constructors",
782 vec![
783 single_example(
784 "Left Rounded",
785 ButtonLike::new_rounded_left("left_rounded")
786 .child(Label::new("Left Rounded"))
787 .style(ButtonStyle::Filled)
788 .into_any_element(),
789 ),
790 single_example(
791 "Right Rounded",
792 ButtonLike::new_rounded_right("right_rounded")
793 .child(Label::new("Right Rounded"))
794 .style(ButtonStyle::Filled)
795 .into_any_element(),
796 ),
797 single_example(
798 "Button Group",
799 h_flex()
800 .gap_px()
801 .child(
802 ButtonLike::new_rounded_left("bg_left")
803 .child(Label::new("Left"))
804 .style(ButtonStyle::Filled),
805 )
806 .child(
807 ButtonLike::new_rounded_right("bg_right")
808 .child(Label::new("Right"))
809 .style(ButtonStyle::Filled),
810 )
811 .into_any_element(),
812 ),
813 ],
814 ),
815 ])
816 .into_any_element(),
817 )
818 }
819}