button_like.rs

  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}