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