Skip to main content

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