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/// Rounding for a button that may have straight edges.
153#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
154pub(crate) struct ButtonLikeRounding {
155    /// Top-left corner rounding
156    pub top_left: bool,
157    /// Top-right corner rounding
158    pub top_right: bool,
159    /// Bottom-right corner rounding
160    pub bottom_right: bool,
161    /// Bottom-left corner rounding
162    pub bottom_left: bool,
163}
164
165impl ButtonLikeRounding {
166    pub const ALL: Self = Self {
167        top_left: true,
168        top_right: true,
169        bottom_right: true,
170        bottom_left: true,
171    };
172    pub const LEFT: Self = Self {
173        top_left: true,
174        top_right: false,
175        bottom_right: false,
176        bottom_left: true,
177    };
178    pub const RIGHT: Self = Self {
179        top_left: false,
180        top_right: true,
181        bottom_right: true,
182        bottom_left: false,
183    };
184}
185
186#[derive(Debug, Clone)]
187pub(crate) struct ButtonLikeStyles {
188    pub background: Hsla,
189    #[allow(unused)]
190    pub border_color: Hsla,
191    #[allow(unused)]
192    pub label_color: Hsla,
193    #[allow(unused)]
194    pub icon_color: Hsla,
195}
196
197fn element_bg_from_elevation(elevation: Option<ElevationIndex>, cx: &mut App) -> Hsla {
198    match elevation {
199        Some(ElevationIndex::Background) => cx.theme().colors().element_background,
200        Some(ElevationIndex::ElevatedSurface) => cx.theme().colors().elevated_surface_background,
201        Some(ElevationIndex::Surface) => cx.theme().colors().surface_background,
202        Some(ElevationIndex::ModalSurface) => cx.theme().colors().background,
203        _ => cx.theme().colors().element_background,
204    }
205}
206
207impl ButtonStyle {
208    pub(crate) fn enabled(
209        self,
210        elevation: Option<ElevationIndex>,
211
212        cx: &mut App,
213    ) -> ButtonLikeStyles {
214        match self {
215            ButtonStyle::Filled => ButtonLikeStyles {
216                background: element_bg_from_elevation(elevation, cx),
217                border_color: transparent_black(),
218                label_color: Color::Default.color(cx),
219                icon_color: Color::Default.color(cx),
220            },
221            ButtonStyle::Tinted(tint) => tint.button_like_style(cx),
222            ButtonStyle::Outlined => ButtonLikeStyles {
223                background: element_bg_from_elevation(elevation, cx),
224                border_color: cx.theme().colors().border_variant,
225                label_color: Color::Default.color(cx),
226                icon_color: Color::Default.color(cx),
227            },
228            ButtonStyle::OutlinedGhost => ButtonLikeStyles {
229                background: transparent_black(),
230                border_color: cx.theme().colors().border_variant,
231                label_color: Color::Default.color(cx),
232                icon_color: Color::Default.color(cx),
233            },
234            ButtonStyle::Subtle => ButtonLikeStyles {
235                background: cx.theme().colors().ghost_element_background,
236                border_color: transparent_black(),
237                label_color: Color::Default.color(cx),
238                icon_color: Color::Default.color(cx),
239            },
240            ButtonStyle::Transparent => ButtonLikeStyles {
241                background: transparent_black(),
242                border_color: transparent_black(),
243                label_color: Color::Default.color(cx),
244                icon_color: Color::Default.color(cx),
245            },
246        }
247    }
248
249    pub(crate) fn hovered(
250        self,
251        elevation: Option<ElevationIndex>,
252
253        cx: &mut App,
254    ) -> ButtonLikeStyles {
255        match self {
256            ButtonStyle::Filled => {
257                let mut filled_background = element_bg_from_elevation(elevation, cx);
258                filled_background.fade_out(0.5);
259
260                ButtonLikeStyles {
261                    background: filled_background,
262                    border_color: transparent_black(),
263                    label_color: Color::Default.color(cx),
264                    icon_color: Color::Default.color(cx),
265                }
266            }
267            ButtonStyle::Tinted(tint) => {
268                let mut styles = tint.button_like_style(cx);
269                let theme = cx.theme();
270                styles.background = theme.darken(styles.background, 0.05, 0.2);
271                styles
272            }
273            ButtonStyle::Outlined => ButtonLikeStyles {
274                background: cx.theme().colors().ghost_element_hover,
275                border_color: cx.theme().colors().border,
276                label_color: Color::Default.color(cx),
277                icon_color: Color::Default.color(cx),
278            },
279            ButtonStyle::OutlinedGhost => ButtonLikeStyles {
280                background: cx.theme().colors().ghost_element_hover,
281                border_color: cx.theme().colors().border,
282                label_color: Color::Default.color(cx),
283                icon_color: Color::Default.color(cx),
284            },
285            ButtonStyle::Subtle => ButtonLikeStyles {
286                background: cx.theme().colors().ghost_element_hover,
287                border_color: transparent_black(),
288                label_color: Color::Default.color(cx),
289                icon_color: Color::Default.color(cx),
290            },
291            ButtonStyle::Transparent => ButtonLikeStyles {
292                background: transparent_black(),
293                border_color: transparent_black(),
294                // TODO: These are not great
295                label_color: Color::Muted.color(cx),
296                // TODO: These are not great
297                icon_color: Color::Muted.color(cx),
298            },
299        }
300    }
301
302    pub(crate) fn active(self, cx: &mut App) -> ButtonLikeStyles {
303        match self {
304            ButtonStyle::Filled => ButtonLikeStyles {
305                background: cx.theme().colors().element_active,
306                border_color: transparent_black(),
307                label_color: Color::Default.color(cx),
308                icon_color: Color::Default.color(cx),
309            },
310            ButtonStyle::Tinted(tint) => tint.button_like_style(cx),
311            ButtonStyle::Subtle => ButtonLikeStyles {
312                background: cx.theme().colors().ghost_element_active,
313                border_color: transparent_black(),
314                label_color: Color::Default.color(cx),
315                icon_color: Color::Default.color(cx),
316            },
317            ButtonStyle::Outlined => ButtonLikeStyles {
318                background: cx.theme().colors().element_active,
319                border_color: cx.theme().colors().border_variant,
320                label_color: Color::Default.color(cx),
321                icon_color: Color::Default.color(cx),
322            },
323            ButtonStyle::OutlinedGhost => ButtonLikeStyles {
324                background: transparent_black(),
325                border_color: cx.theme().colors().border_variant,
326                label_color: Color::Default.color(cx),
327                icon_color: Color::Default.color(cx),
328            },
329            ButtonStyle::Transparent => ButtonLikeStyles {
330                background: transparent_black(),
331                border_color: transparent_black(),
332                // TODO: These are not great
333                label_color: Color::Muted.color(cx),
334                // TODO: These are not great
335                icon_color: Color::Muted.color(cx),
336            },
337        }
338    }
339
340    #[allow(unused)]
341    pub(crate) fn focused(self, window: &mut Window, cx: &mut App) -> ButtonLikeStyles {
342        match self {
343            ButtonStyle::Filled => ButtonLikeStyles {
344                background: cx.theme().colors().element_background,
345                border_color: cx.theme().colors().border_focused,
346                label_color: Color::Default.color(cx),
347                icon_color: Color::Default.color(cx),
348            },
349            ButtonStyle::Tinted(tint) => tint.button_like_style(cx),
350            ButtonStyle::Subtle => ButtonLikeStyles {
351                background: cx.theme().colors().ghost_element_background,
352                border_color: cx.theme().colors().border_focused,
353                label_color: Color::Default.color(cx),
354                icon_color: Color::Default.color(cx),
355            },
356            ButtonStyle::Outlined => ButtonLikeStyles {
357                background: cx.theme().colors().ghost_element_background,
358                border_color: cx.theme().colors().border,
359                label_color: Color::Default.color(cx),
360                icon_color: Color::Default.color(cx),
361            },
362            ButtonStyle::OutlinedGhost => ButtonLikeStyles {
363                background: transparent_black(),
364                border_color: cx.theme().colors().border,
365                label_color: Color::Default.color(cx),
366                icon_color: Color::Default.color(cx),
367            },
368            ButtonStyle::Transparent => ButtonLikeStyles {
369                background: transparent_black(),
370                border_color: cx.theme().colors().border_focused,
371                label_color: Color::Accent.color(cx),
372                icon_color: Color::Accent.color(cx),
373            },
374        }
375    }
376
377    #[allow(unused)]
378    pub(crate) fn disabled(
379        self,
380        elevation: Option<ElevationIndex>,
381        window: &mut Window,
382        cx: &mut App,
383    ) -> ButtonLikeStyles {
384        match self {
385            ButtonStyle::Filled => ButtonLikeStyles {
386                background: cx.theme().colors().element_disabled,
387                border_color: cx.theme().colors().border_disabled,
388                label_color: Color::Disabled.color(cx),
389                icon_color: Color::Disabled.color(cx),
390            },
391            ButtonStyle::Tinted(tint) => tint.button_like_style(cx),
392            ButtonStyle::Subtle => ButtonLikeStyles {
393                background: cx.theme().colors().ghost_element_disabled,
394                border_color: cx.theme().colors().border_disabled,
395                label_color: Color::Disabled.color(cx),
396                icon_color: Color::Disabled.color(cx),
397            },
398            ButtonStyle::Outlined => ButtonLikeStyles {
399                background: cx.theme().colors().element_disabled,
400                border_color: cx.theme().colors().border_disabled,
401                label_color: Color::Default.color(cx),
402                icon_color: Color::Default.color(cx),
403            },
404            ButtonStyle::OutlinedGhost => ButtonLikeStyles {
405                background: transparent_black(),
406                border_color: cx.theme().colors().border_disabled,
407                label_color: Color::Default.color(cx),
408                icon_color: Color::Default.color(cx),
409            },
410            ButtonStyle::Transparent => ButtonLikeStyles {
411                background: transparent_black(),
412                border_color: transparent_black(),
413                label_color: Color::Disabled.color(cx),
414                icon_color: Color::Disabled.color(cx),
415            },
416        }
417    }
418}
419
420/// The height of a button.
421///
422/// Can also be used to size non-button elements to align with [`Button`]s.
423#[derive(Default, PartialEq, Clone, Copy)]
424pub enum ButtonSize {
425    Large,
426    Medium,
427    #[default]
428    Default,
429    Compact,
430    None,
431}
432
433impl ButtonSize {
434    pub fn rems(self) -> Rems {
435        match self {
436            ButtonSize::Large => rems_from_px(32.),
437            ButtonSize::Medium => rems_from_px(28.),
438            ButtonSize::Default => rems_from_px(22.),
439            ButtonSize::Compact => rems_from_px(18.),
440            ButtonSize::None => rems_from_px(16.),
441        }
442    }
443}
444
445/// A button-like element that can be used to create a custom button when
446/// prebuilt buttons are not sufficient. Use this sparingly, as it is
447/// unconstrained and may make the UI feel less consistent.
448///
449/// This is also used to build the prebuilt buttons.
450#[derive(IntoElement, Documented, RegisterComponent)]
451pub struct ButtonLike {
452    pub(super) base: Div,
453    id: ElementId,
454    pub(super) style: ButtonStyle,
455    pub(super) disabled: bool,
456    pub(super) selected: bool,
457    pub(super) selected_style: Option<ButtonStyle>,
458    pub(super) width: Option<DefiniteLength>,
459    pub(super) height: Option<DefiniteLength>,
460    pub(super) layer: Option<ElevationIndex>,
461    tab_index: Option<isize>,
462    size: ButtonSize,
463    rounding: Option<ButtonLikeRounding>,
464    tooltip: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyView>>,
465    hoverable_tooltip: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyView>>,
466    cursor_style: CursorStyle,
467    on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
468    on_right_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
469    children: SmallVec<[AnyElement; 2]>,
470    focus_handle: Option<FocusHandle>,
471}
472
473impl ButtonLike {
474    pub fn new(id: impl Into<ElementId>) -> Self {
475        Self {
476            base: div(),
477            id: id.into(),
478            style: ButtonStyle::default(),
479            disabled: false,
480            selected: false,
481            selected_style: None,
482            width: None,
483            height: None,
484            size: ButtonSize::Default,
485            rounding: Some(ButtonLikeRounding::ALL),
486            tooltip: None,
487            hoverable_tooltip: None,
488            children: SmallVec::new(),
489            cursor_style: CursorStyle::PointingHand,
490            on_click: None,
491            on_right_click: None,
492            layer: None,
493            tab_index: None,
494            focus_handle: None,
495        }
496    }
497
498    pub fn new_rounded_left(id: impl Into<ElementId>) -> Self {
499        Self::new(id).rounding(ButtonLikeRounding::LEFT)
500    }
501
502    pub fn new_rounded_right(id: impl Into<ElementId>) -> Self {
503        Self::new(id).rounding(ButtonLikeRounding::RIGHT)
504    }
505
506    pub fn new_rounded_all(id: impl Into<ElementId>) -> Self {
507        Self::new(id).rounding(ButtonLikeRounding::ALL)
508    }
509
510    pub fn opacity(mut self, opacity: f32) -> Self {
511        self.base = self.base.opacity(opacity);
512        self
513    }
514
515    pub fn height(mut self, height: DefiniteLength) -> Self {
516        self.height = Some(height);
517        self
518    }
519
520    pub(crate) fn rounding(mut self, rounding: impl Into<Option<ButtonLikeRounding>>) -> Self {
521        self.rounding = rounding.into();
522        self
523    }
524
525    pub fn on_right_click(
526        mut self,
527        handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
528    ) -> Self {
529        self.on_right_click = Some(Box::new(handler));
530        self
531    }
532
533    pub fn hoverable_tooltip(
534        mut self,
535        tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
536    ) -> Self {
537        self.hoverable_tooltip = Some(Box::new(tooltip));
538        self
539    }
540}
541
542impl Disableable for ButtonLike {
543    fn disabled(mut self, disabled: bool) -> Self {
544        self.disabled = disabled;
545        self
546    }
547}
548
549impl Toggleable for ButtonLike {
550    fn toggle_state(mut self, selected: bool) -> Self {
551        self.selected = selected;
552        self
553    }
554}
555
556impl SelectableButton for ButtonLike {
557    fn selected_style(mut self, style: ButtonStyle) -> Self {
558        self.selected_style = Some(style);
559        self
560    }
561}
562
563impl Clickable for ButtonLike {
564    fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self {
565        self.on_click = Some(Box::new(handler));
566        self
567    }
568
569    fn cursor_style(mut self, cursor_style: CursorStyle) -> Self {
570        self.cursor_style = cursor_style;
571        self
572    }
573}
574
575impl FixedWidth for ButtonLike {
576    fn width(mut self, width: impl Into<DefiniteLength>) -> Self {
577        self.width = Some(width.into());
578        self
579    }
580
581    fn full_width(mut self) -> Self {
582        self.width = Some(relative(1.));
583        self
584    }
585}
586
587impl ButtonCommon for ButtonLike {
588    fn id(&self) -> &ElementId {
589        &self.id
590    }
591
592    fn style(mut self, style: ButtonStyle) -> Self {
593        self.style = style;
594        self
595    }
596
597    fn size(mut self, size: ButtonSize) -> Self {
598        self.size = size;
599        self
600    }
601
602    fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
603        self.tooltip = Some(Box::new(tooltip));
604        self
605    }
606
607    fn tab_index(mut self, tab_index: impl Into<isize>) -> Self {
608        self.tab_index = Some(tab_index.into());
609        self
610    }
611
612    fn layer(mut self, elevation: ElevationIndex) -> Self {
613        self.layer = Some(elevation);
614        self
615    }
616
617    fn track_focus(mut self, focus_handle: &gpui::FocusHandle) -> Self {
618        self.focus_handle = Some(focus_handle.clone());
619        self
620    }
621}
622
623impl VisibleOnHover for ButtonLike {
624    fn visible_on_hover(mut self, group_name: impl Into<SharedString>) -> Self {
625        self.base = self.base.visible_on_hover(group_name);
626        self
627    }
628}
629
630impl ParentElement for ButtonLike {
631    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
632        self.children.extend(elements)
633    }
634}
635
636impl RenderOnce for ButtonLike {
637    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
638        let style = self
639            .selected_style
640            .filter(|_| self.selected)
641            .unwrap_or(self.style);
642
643        let is_outlined = matches!(
644            self.style,
645            ButtonStyle::Outlined | ButtonStyle::OutlinedGhost
646        );
647
648        self.base
649            .h_flex()
650            .id(self.id.clone())
651            .when_some(self.tab_index, |this, tab_index| this.tab_index(tab_index))
652            .when_some(self.focus_handle, |this, focus_handle| {
653                this.track_focus(&focus_handle)
654            })
655            .font_ui(cx)
656            .group("")
657            .flex_none()
658            .h(self.height.unwrap_or(self.size.rems().into()))
659            .when_some(self.width, |this, width| {
660                this.w(width).justify_center().text_center()
661            })
662            .when(is_outlined, |this| this.border_1())
663            .when_some(self.rounding, |this, rounding| {
664                this.when(rounding.top_left, |this| this.rounded_tl_sm())
665                    .when(rounding.top_right, |this| this.rounded_tr_sm())
666                    .when(rounding.bottom_right, |this| this.rounded_br_sm())
667                    .when(rounding.bottom_left, |this| this.rounded_bl_sm())
668            })
669            .gap(DynamicSpacing::Base04.rems(cx))
670            .map(|this| match self.size {
671                ButtonSize::Large | ButtonSize::Medium => this.px(DynamicSpacing::Base08.rems(cx)),
672                ButtonSize::Default | ButtonSize::Compact => {
673                    this.px(DynamicSpacing::Base04.rems(cx))
674                }
675                ButtonSize::None => this.px_px(),
676            })
677            .border_color(style.enabled(self.layer, cx).border_color)
678            .bg(style.enabled(self.layer, cx).background)
679            .when(self.disabled, |this| {
680                if self.cursor_style == CursorStyle::PointingHand {
681                    this.cursor_not_allowed()
682                } else {
683                    this.cursor(self.cursor_style)
684                }
685            })
686            .when(!self.disabled, |this| {
687                let hovered_style = style.hovered(self.layer, cx);
688                let focus_color =
689                    |refinement: StyleRefinement| refinement.bg(hovered_style.background);
690
691                this.cursor(self.cursor_style)
692                    .hover(focus_color)
693                    .map(|this| {
694                        if is_outlined {
695                            this.focus_visible(|s| {
696                                s.border_color(cx.theme().colors().border_focused)
697                            })
698                        } else {
699                            this.focus_visible(focus_color)
700                        }
701                    })
702                    .active(|active| active.bg(style.active(cx).background))
703            })
704            .when_some(
705                self.on_right_click.filter(|_| !self.disabled),
706                |this, on_right_click| {
707                    this.on_mouse_down(MouseButton::Right, |_event, window, cx| {
708                        window.prevent_default();
709                        cx.stop_propagation();
710                    })
711                    .on_mouse_up(
712                        MouseButton::Right,
713                        move |event, window, cx| {
714                            cx.stop_propagation();
715                            let click_event = ClickEvent::Mouse(MouseClickEvent {
716                                down: MouseDownEvent {
717                                    button: MouseButton::Right,
718                                    position: event.position,
719                                    modifiers: event.modifiers,
720                                    click_count: 1,
721                                    first_mouse: false,
722                                },
723                                up: MouseUpEvent {
724                                    button: MouseButton::Right,
725                                    position: event.position,
726                                    modifiers: event.modifiers,
727                                    click_count: 1,
728                                },
729                            });
730                            (on_right_click)(&click_event, window, cx)
731                        },
732                    )
733                },
734            )
735            .when_some(
736                self.on_click.filter(|_| !self.disabled),
737                |this, on_click| {
738                    this.on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
739                        .on_click(move |event, window, cx| {
740                            cx.stop_propagation();
741                            (on_click)(event, window, cx)
742                        })
743                },
744            )
745            .when_some(self.tooltip, |this, tooltip| {
746                this.tooltip(move |window, cx| tooltip(window, cx))
747            })
748            .when_some(self.hoverable_tooltip, |this, tooltip| {
749                this.hoverable_tooltip(move |window, cx| tooltip(window, cx))
750            })
751            .children(self.children)
752    }
753}
754
755impl Component for ButtonLike {
756    fn scope() -> ComponentScope {
757        ComponentScope::Input
758    }
759
760    fn sort_name() -> &'static str {
761        // ButtonLike should be at the bottom of the button list
762        "ButtonZ"
763    }
764
765    fn description() -> Option<&'static str> {
766        Some(ButtonLike::DOCS)
767    }
768
769    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
770        Some(
771            v_flex()
772                .gap_6()
773                .children(vec![
774                    example_group(vec![
775                        single_example(
776                            "Default",
777                            ButtonLike::new("default")
778                                .child(Label::new("Default"))
779                                .into_any_element(),
780                        ),
781                        single_example(
782                            "Filled",
783                            ButtonLike::new("filled")
784                                .style(ButtonStyle::Filled)
785                                .child(Label::new("Filled"))
786                                .into_any_element(),
787                        ),
788                        single_example(
789                            "Subtle",
790                            ButtonLike::new("outline")
791                                .style(ButtonStyle::Subtle)
792                                .child(Label::new("Subtle"))
793                                .into_any_element(),
794                        ),
795                        single_example(
796                            "Tinted",
797                            ButtonLike::new("tinted_accent_style")
798                                .style(ButtonStyle::Tinted(TintColor::Accent))
799                                .child(Label::new("Accent"))
800                                .into_any_element(),
801                        ),
802                        single_example(
803                            "Transparent",
804                            ButtonLike::new("transparent")
805                                .style(ButtonStyle::Transparent)
806                                .child(Label::new("Transparent"))
807                                .into_any_element(),
808                        ),
809                    ]),
810                    example_group_with_title(
811                        "Button Group Constructors",
812                        vec![
813                            single_example(
814                                "Left Rounded",
815                                ButtonLike::new_rounded_left("left_rounded")
816                                    .child(Label::new("Left Rounded"))
817                                    .style(ButtonStyle::Filled)
818                                    .into_any_element(),
819                            ),
820                            single_example(
821                                "Right Rounded",
822                                ButtonLike::new_rounded_right("right_rounded")
823                                    .child(Label::new("Right Rounded"))
824                                    .style(ButtonStyle::Filled)
825                                    .into_any_element(),
826                            ),
827                            single_example(
828                                "Button Group",
829                                h_flex()
830                                    .gap_px()
831                                    .child(
832                                        ButtonLike::new_rounded_left("bg_left")
833                                            .child(Label::new("Left"))
834                                            .style(ButtonStyle::Filled),
835                                    )
836                                    .child(
837                                        ButtonLike::new_rounded_right("bg_right")
838                                            .child(Label::new("Right"))
839                                            .style(ButtonStyle::Filled),
840                                    )
841                                    .into_any_element(),
842                            ),
843                        ],
844                    ),
845                ])
846                .into_any_element(),
847        )
848    }
849}