button_like.rs

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