gpui_component/button/
button.rs

1use std::rc::Rc;
2
3use crate::{
4    h_flex, spinner::Spinner, tooltip::Tooltip, ActiveTheme, Colorize as _, Disableable,
5    FocusableExt as _, Icon, Selectable, Sizable, Size, StyleSized, StyledExt,
6};
7use gpui::{
8    div, prelude::FluentBuilder as _, px, relative, Action, AnyElement, App, ClickEvent, Corners,
9    Div, Edges, ElementId, Hsla, InteractiveElement, Interactivity, IntoElement, ParentElement,
10    Pixels, RenderOnce, SharedString, Stateful, StatefulInteractiveElement as _, StyleRefinement,
11    Styled, Window,
12};
13
14#[derive(Default, Clone, Copy)]
15pub enum ButtonRounded {
16    None,
17    Small,
18    #[default]
19    Medium,
20    Large,
21    Size(Pixels),
22}
23
24impl From<Pixels> for ButtonRounded {
25    fn from(px: Pixels) -> Self {
26        ButtonRounded::Size(px)
27    }
28}
29
30#[derive(Clone, Copy, PartialEq, Eq)]
31pub struct ButtonCustomVariant {
32    color: Hsla,
33    foreground: Hsla,
34    border: Hsla,
35    shadow: bool,
36    hover: Hsla,
37    active: Hsla,
38}
39
40pub trait ButtonVariants: Sized {
41    fn with_variant(self, variant: ButtonVariant) -> Self;
42
43    /// With the primary style for the Button.
44    fn primary(self) -> Self {
45        self.with_variant(ButtonVariant::Primary)
46    }
47
48    /// With the danger style for the Button.
49    fn danger(self) -> Self {
50        self.with_variant(ButtonVariant::Danger)
51    }
52
53    /// With the warning style for the Button.
54    fn warning(self) -> Self {
55        self.with_variant(ButtonVariant::Warning)
56    }
57
58    /// With the success style for the Button.
59    fn success(self) -> Self {
60        self.with_variant(ButtonVariant::Success)
61    }
62
63    /// With the info style for the Button.
64    fn info(self) -> Self {
65        self.with_variant(ButtonVariant::Info)
66    }
67
68    /// With the ghost style for the Button.
69    fn ghost(self) -> Self {
70        self.with_variant(ButtonVariant::Ghost)
71    }
72
73    /// With the link style for the Button.
74    fn link(self) -> Self {
75        self.with_variant(ButtonVariant::Link)
76    }
77
78    /// With the text style for the Button, it will no padding look like a normal text.
79    fn text(self) -> Self {
80        self.with_variant(ButtonVariant::Text)
81    }
82
83    /// With the custom style for the Button.
84    fn custom(self, style: ButtonCustomVariant) -> Self {
85        self.with_variant(ButtonVariant::Custom(style))
86    }
87}
88
89impl ButtonCustomVariant {
90    pub fn new(cx: &App) -> Self {
91        Self {
92            color: cx.theme().transparent,
93            foreground: cx.theme().foreground,
94            border: cx.theme().transparent,
95            hover: cx.theme().transparent,
96            active: cx.theme().transparent,
97            shadow: false,
98        }
99    }
100
101    /// Set background color, default is transparent.
102    pub fn color(mut self, color: Hsla) -> Self {
103        self.color = color;
104        self
105    }
106
107    /// Set foreground color, default is theme foreground.
108    pub fn foreground(mut self, color: Hsla) -> Self {
109        self.foreground = color;
110        self
111    }
112
113    /// Set border color, default is transparent.
114    pub fn border(mut self, color: Hsla) -> Self {
115        self.border = color;
116        self
117    }
118
119    /// Set hover background color, default is transparent.
120    pub fn hover(mut self, color: Hsla) -> Self {
121        self.hover = color;
122        self
123    }
124
125    /// Set active background color, default is transparent.
126    pub fn active(mut self, color: Hsla) -> Self {
127        self.active = color;
128        self
129    }
130
131    /// Set shadow, default is false.
132    pub fn shadow(mut self, shadow: bool) -> Self {
133        self.shadow = shadow;
134        self
135    }
136}
137
138/// The veriant of the Button.
139#[derive(Clone, Copy, PartialEq, Eq, Default)]
140pub enum ButtonVariant {
141    Primary,
142    #[default]
143    Secondary,
144    Danger,
145    Info,
146    Success,
147    Warning,
148    Ghost,
149    Link,
150    Text,
151    Custom(ButtonCustomVariant),
152}
153
154impl ButtonVariant {
155    #[inline]
156    pub fn is_link(&self) -> bool {
157        matches!(self, Self::Link)
158    }
159
160    #[inline]
161    pub fn is_text(&self) -> bool {
162        matches!(self, Self::Text)
163    }
164
165    #[inline]
166    pub fn is_ghost(&self) -> bool {
167        matches!(self, Self::Ghost)
168    }
169
170    #[inline]
171    fn no_padding(&self) -> bool {
172        self.is_link() || self.is_text()
173    }
174}
175
176/// A Button element.
177#[derive(IntoElement)]
178pub struct Button {
179    id: ElementId,
180    base: Stateful<Div>,
181    style: StyleRefinement,
182    icon: Option<Icon>,
183    label: Option<SharedString>,
184    children: Vec<AnyElement>,
185    disabled: bool,
186    pub(crate) selected: bool,
187    variant: ButtonVariant,
188    rounded: ButtonRounded,
189    outline: bool,
190    border_corners: Corners<bool>,
191    border_edges: Edges<bool>,
192    size: Size,
193    compact: bool,
194    tooltip: Option<(
195        SharedString,
196        Option<(Rc<Box<dyn Action>>, Option<SharedString>)>,
197    )>,
198    on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
199    on_hover: Option<Rc<dyn Fn(&bool, &mut Window, &mut App)>>,
200    loading: bool,
201    loading_icon: Option<Icon>,
202
203    tab_index: isize,
204    tab_stop: bool,
205}
206
207impl From<Button> for AnyElement {
208    fn from(button: Button) -> Self {
209        button.into_any_element()
210    }
211}
212
213impl Button {
214    pub fn new(id: impl Into<ElementId>) -> Self {
215        let id = id.into();
216
217        Self {
218            id: id.clone(),
219            // ID must be set after div is created;
220            // `dropdown_menu` uses this id to create the popup menu.
221            base: div().flex_shrink_0().id(id),
222            style: StyleRefinement::default(),
223            icon: None,
224            label: None,
225            disabled: false,
226            selected: false,
227            variant: ButtonVariant::default(),
228            rounded: ButtonRounded::Medium,
229            border_corners: Corners::all(true),
230            border_edges: Edges::all(true),
231            size: Size::Medium,
232            tooltip: None,
233            on_click: None,
234            on_hover: None,
235            loading: false,
236            compact: false,
237            outline: false,
238            children: Vec::new(),
239            loading_icon: None,
240            tab_index: 0,
241            tab_stop: true,
242        }
243    }
244
245    /// Set the outline style of the Button.
246    pub fn outline(mut self) -> Self {
247        self.outline = true;
248        self
249    }
250
251    /// Set the border radius of the Button.
252    pub fn rounded(mut self, rounded: impl Into<ButtonRounded>) -> Self {
253        self.rounded = rounded.into();
254        self
255    }
256
257    /// Set the border corners side of the Button.
258    pub(crate) fn border_corners(mut self, corners: impl Into<Corners<bool>>) -> Self {
259        self.border_corners = corners.into();
260        self
261    }
262
263    /// Set the border edges of the Button.
264    pub(crate) fn border_edges(mut self, edges: impl Into<Edges<bool>>) -> Self {
265        self.border_edges = edges.into();
266        self
267    }
268
269    /// Set label to the Button, if no label is set, the button will be in Icon Button mode.
270    pub fn label(mut self, label: impl Into<SharedString>) -> Self {
271        self.label = Some(label.into());
272        self
273    }
274
275    /// Set the icon of the button, if the Button have no label, the button well in Icon Button mode.
276    pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
277        self.icon = Some(icon.into());
278        self
279    }
280
281    /// Set the tooltip of the button.
282    pub fn tooltip(mut self, tooltip: impl Into<SharedString>) -> Self {
283        self.tooltip = Some((tooltip.into(), None));
284        self
285    }
286
287    /// Set the tooltip of the button with action to show keybinding.
288    pub fn tooltip_with_action(
289        mut self,
290        tooltip: impl Into<SharedString>,
291        action: &dyn Action,
292        context: Option<&str>,
293    ) -> Self {
294        self.tooltip = Some((
295            tooltip.into(),
296            Some((
297                Rc::new(action.boxed_clone()),
298                context.map(|c| c.to_string().into()),
299            )),
300        ));
301        self
302    }
303
304    /// Set true to show the loading indicator.
305    pub fn loading(mut self, loading: bool) -> Self {
306        self.loading = loading;
307        self
308    }
309
310    /// Set the button to compact mode, then padding will be reduced.
311    pub fn compact(mut self) -> Self {
312        self.compact = true;
313        self
314    }
315
316    /// Add click handler.
317    pub fn on_click(
318        mut self,
319        handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
320    ) -> Self {
321        self.on_click = Some(Rc::new(handler));
322        self
323    }
324
325    /// Add hover handler, the bool parameter indicates whether the mouse is hovering.
326    pub fn on_hover(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self {
327        self.on_hover = Some(Rc::new(handler));
328        self
329    }
330
331    /// Set the loading icon of the button, it will be used when loading is true.
332    ///
333    /// Default is a spinner icon.
334    pub fn loading_icon(mut self, icon: impl Into<Icon>) -> Self {
335        self.loading_icon = Some(icon.into());
336        self
337    }
338
339    /// Set the tab index of the button, it will be used to focus the button by tab key.
340    ///
341    /// Default is 0.
342    pub fn tab_index(mut self, tab_index: isize) -> Self {
343        self.tab_index = tab_index;
344        self
345    }
346
347    /// Set the tab stop of the button, if true, the button will be focusable by tab key.
348    ///
349    /// Default is true.
350    pub fn tab_stop(mut self, tab_stop: bool) -> Self {
351        self.tab_stop = tab_stop;
352        self
353    }
354
355    #[inline]
356    fn clickable(&self) -> bool {
357        !(self.disabled || self.loading) && self.on_click.is_some()
358    }
359
360    #[inline]
361    fn hoverable(&self) -> bool {
362        !(self.disabled || self.loading) && self.on_hover.is_some()
363    }
364}
365
366impl Disableable for Button {
367    fn disabled(mut self, disabled: bool) -> Self {
368        self.disabled = disabled;
369        self
370    }
371}
372
373impl Selectable for Button {
374    fn selected(mut self, selected: bool) -> Self {
375        self.selected = selected;
376        self
377    }
378
379    fn is_selected(&self) -> bool {
380        self.selected
381    }
382}
383
384impl Sizable for Button {
385    fn with_size(mut self, size: impl Into<Size>) -> Self {
386        self.size = size.into();
387        self
388    }
389}
390
391impl ButtonVariants for Button {
392    fn with_variant(mut self, variant: ButtonVariant) -> Self {
393        self.variant = variant;
394        self
395    }
396}
397
398impl Styled for Button {
399    fn style(&mut self) -> &mut StyleRefinement {
400        &mut self.style
401    }
402}
403
404impl ParentElement for Button {
405    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
406        self.children.extend(elements)
407    }
408}
409
410impl InteractiveElement for Button {
411    fn interactivity(&mut self) -> &mut Interactivity {
412        self.base.interactivity()
413    }
414}
415
416impl RenderOnce for Button {
417    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
418        let style: ButtonVariant = self.variant;
419        let clickable = self.clickable();
420        let hoverable = self.hoverable();
421        let normal_style = style.normal(self.outline, cx);
422        let icon_size = match self.size {
423            Size::Size(v) => Size::Size(v * 0.75),
424            _ => self.size,
425        };
426
427        let focus_handle = window
428            .use_keyed_state(self.id.clone(), cx, |_, cx| cx.focus_handle())
429            .read(cx)
430            .clone();
431        let is_focused = focus_handle.is_focused(window);
432
433        self.base
434            .when(!self.disabled, |this| {
435                this.track_focus(
436                    &focus_handle
437                        .tab_index(self.tab_index)
438                        .tab_stop(self.tab_stop),
439                )
440            })
441            .cursor_default()
442            .flex()
443            .flex_shrink_0()
444            .items_center()
445            .justify_center()
446            .cursor_default()
447            .when(self.variant.is_link(), |this| this.cursor_pointer())
448            .when(cx.theme().shadow && normal_style.shadow, |this| {
449                this.shadow_xs()
450            })
451            .when(!style.no_padding(), |this| {
452                if self.label.is_none() && self.children.is_empty() {
453                    // Icon Button
454                    match self.size {
455                        Size::Size(px) => this.size(px),
456                        Size::XSmall => this.size_5(),
457                        Size::Small => this.size_6(),
458                        Size::Large | Size::Medium => this.size_8(),
459                    }
460                } else {
461                    // Normal Button
462                    match self.size {
463                        Size::Size(size) => this.px(size * 0.2),
464                        Size::XSmall => this.h_5().px_1(),
465                        Size::Small => this.h_6().px_3().when(self.compact, |this| this.px_1p5()),
466                        _ => this.h_8().px_4().when(self.compact, |this| this.px_2()),
467                    }
468                }
469            })
470            .when(
471                self.border_corners.top_left && self.border_corners.bottom_left,
472                |this| match self.rounded {
473                    ButtonRounded::Small => this.rounded_l(cx.theme().radius * 0.5),
474                    ButtonRounded::Medium => this.rounded_l(cx.theme().radius),
475                    ButtonRounded::Large => this.rounded_l(cx.theme().radius * 2.0),
476                    ButtonRounded::Size(px) => this.rounded_l(px),
477                    ButtonRounded::None => this.rounded_none(),
478                },
479            )
480            .when(
481                self.border_corners.top_right && self.border_corners.bottom_right,
482                |this| match self.rounded {
483                    ButtonRounded::Small => this.rounded_r(cx.theme().radius * 0.5),
484                    ButtonRounded::Medium => this.rounded_r(cx.theme().radius),
485                    ButtonRounded::Large => this.rounded_r(cx.theme().radius * 2.0),
486                    ButtonRounded::Size(px) => this.rounded_r(px),
487                    ButtonRounded::None => this.rounded_none(),
488                },
489            )
490            .when(self.border_edges.left, |this| this.border_l_1())
491            .when(self.border_edges.right, |this| this.border_r_1())
492            .when(self.border_edges.top, |this| this.border_t_1())
493            .when(self.border_edges.bottom, |this| this.border_b_1())
494            .text_color(normal_style.fg)
495            .when(self.selected, |this| {
496                let selected_style = style.selected(self.outline, cx);
497                this.bg(selected_style.bg)
498                    .border_color(selected_style.border)
499                    .text_color(selected_style.fg)
500            })
501            .when(!self.disabled && !self.selected, |this| {
502                this.border_color(normal_style.border)
503                    .bg(normal_style.bg)
504                    .when(normal_style.underline, |this| this.text_decoration_1())
505                    .hover(|this| {
506                        let hover_style = style.hovered(self.outline, cx);
507                        this.bg(hover_style.bg)
508                            .border_color(hover_style.border)
509                            .text_color(crate::red_400())
510                    })
511                    .active(|this| {
512                        let active_style = style.active(self.outline, cx);
513                        this.bg(active_style.bg)
514                            .border_color(active_style.border)
515                            .text_color(active_style.fg)
516                    })
517            })
518            .when(self.disabled, |this| {
519                let disabled_style = style.disabled(self.outline, cx);
520                this.bg(disabled_style.bg)
521                    .text_color(disabled_style.fg)
522                    .border_color(disabled_style.border)
523                    .shadow_none()
524            })
525            .refine_style(&self.style)
526            .on_mouse_down(gpui::MouseButton::Left, |_, window, _| {
527                // Avoid focus on mouse down.
528                window.prevent_default();
529            })
530            .when_some(self.on_click.filter(|_| clickable), |this, on_click| {
531                this.on_click(move |event, window, cx| {
532                    (on_click)(event, window, cx);
533                })
534            })
535            .when_some(self.on_hover.filter(|_| hoverable), |this, on_hover| {
536                this.on_hover(move |hovered, window, cx| {
537                    (on_hover)(hovered, window, cx);
538                })
539            })
540            .child({
541                h_flex()
542                    .id("label")
543                    .items_center()
544                    .justify_center()
545                    .button_text_size(self.size)
546                    .map(|this| match self.size {
547                        Size::XSmall => this.gap_1(),
548                        Size::Small => this.gap_1(),
549                        _ => this.gap_2(),
550                    })
551                    .when(!self.loading, |this| {
552                        this.when_some(self.icon, |this, icon| {
553                            this.child(icon.with_size(icon_size))
554                        })
555                    })
556                    .when(self.loading, |this| {
557                        this.child(
558                            Spinner::new()
559                                .with_size(self.size)
560                                .when_some(self.loading_icon, |this, icon| this.icon(icon)),
561                        )
562                    })
563                    .when_some(self.label, |this, label| {
564                        this.child(div().flex_none().line_height(relative(1.)).child(label))
565                    })
566                    .children(self.children)
567            })
568            .when(self.loading && !self.disabled, |this| {
569                this.bg(normal_style.bg.opacity(0.8))
570                    .border_color(normal_style.border.opacity(0.8))
571                    .text_color(normal_style.fg.opacity(0.8))
572            })
573            .when_some(self.tooltip, |this, (tooltip, action)| {
574                this.tooltip(move |window, cx| {
575                    Tooltip::new(tooltip.clone())
576                        .when_some(action.clone(), |this, (action, context)| {
577                            this.action(
578                                action.boxed_clone().as_ref(),
579                                context.as_ref().map(|c| c.as_ref()),
580                            )
581                        })
582                        .build(window, cx)
583                })
584            })
585            .focus_ring(is_focused, px(0.), window, cx)
586    }
587}
588
589struct ButtonVariantStyle {
590    bg: Hsla,
591    border: Hsla,
592    fg: Hsla,
593    underline: bool,
594    shadow: bool,
595}
596
597impl ButtonVariant {
598    fn bg_color(&self, outline: bool, cx: &mut App) -> Hsla {
599        if outline {
600            return cx.theme().background;
601        }
602
603        match self {
604            ButtonVariant::Primary => cx.theme().primary,
605            ButtonVariant::Secondary => cx.theme().secondary,
606            ButtonVariant::Danger => cx.theme().danger,
607            ButtonVariant::Warning => cx.theme().warning,
608            ButtonVariant::Success => cx.theme().success,
609            ButtonVariant::Info => cx.theme().info,
610            ButtonVariant::Ghost | ButtonVariant::Link | ButtonVariant::Text => {
611                cx.theme().transparent
612            }
613            ButtonVariant::Custom(colors) => colors.color,
614        }
615    }
616
617    fn text_color(&self, outline: bool, cx: &mut App) -> Hsla {
618        match self {
619            ButtonVariant::Primary => {
620                if outline {
621                    cx.theme().primary
622                } else {
623                    cx.theme().primary_foreground
624                }
625            }
626            ButtonVariant::Secondary | ButtonVariant::Ghost => cx.theme().secondary_foreground,
627            ButtonVariant::Danger => {
628                if outline {
629                    cx.theme().danger
630                } else {
631                    cx.theme().danger_foreground
632                }
633            }
634            ButtonVariant::Warning => {
635                if outline {
636                    cx.theme().warning
637                } else {
638                    cx.theme().warning_foreground
639                }
640            }
641            ButtonVariant::Success => {
642                if outline {
643                    cx.theme().success
644                } else {
645                    cx.theme().success_foreground
646                }
647            }
648            ButtonVariant::Info => {
649                if outline {
650                    cx.theme().info
651                } else {
652                    cx.theme().info_foreground
653                }
654            }
655            ButtonVariant::Link => cx.theme().link,
656            ButtonVariant::Text => cx.theme().foreground,
657            ButtonVariant::Custom(colors) => {
658                if outline {
659                    colors.color
660                } else {
661                    colors.foreground
662                }
663            }
664        }
665    }
666
667    fn border_color(&self, bg: Hsla, outline: bool, cx: &mut App) -> Hsla {
668        match self {
669            ButtonVariant::Secondary => {
670                if outline {
671                    cx.theme().border
672                } else {
673                    bg
674                }
675            }
676            ButtonVariant::Primary => {
677                if outline {
678                    cx.theme().primary
679                } else {
680                    bg
681                }
682            }
683            ButtonVariant::Danger => {
684                if outline {
685                    cx.theme().danger
686                } else {
687                    bg
688                }
689            }
690            ButtonVariant::Info => {
691                if outline {
692                    cx.theme().info
693                } else {
694                    bg
695                }
696            }
697            ButtonVariant::Warning => {
698                if outline {
699                    cx.theme().warning
700                } else {
701                    bg
702                }
703            }
704            ButtonVariant::Success => {
705                if outline {
706                    cx.theme().success
707                } else {
708                    bg
709                }
710            }
711            ButtonVariant::Ghost | ButtonVariant::Link | ButtonVariant::Text => {
712                cx.theme().transparent
713            }
714            ButtonVariant::Custom(colors) => colors.border,
715        }
716    }
717
718    fn underline(&self, _: &App) -> bool {
719        match self {
720            ButtonVariant::Link => true,
721            _ => false,
722        }
723    }
724
725    fn shadow(&self, outline: bool, _: &App) -> bool {
726        match self {
727            ButtonVariant::Primary | ButtonVariant::Secondary | ButtonVariant::Danger => outline,
728            ButtonVariant::Custom(c) => c.shadow,
729            _ => false,
730        }
731    }
732
733    fn normal(&self, outline: bool, cx: &mut App) -> ButtonVariantStyle {
734        let bg = self.bg_color(outline, cx);
735        let border = self.border_color(bg, outline, cx);
736        let fg = self.text_color(outline, cx);
737        let underline = self.underline(cx);
738        let shadow = self.shadow(outline, cx);
739
740        ButtonVariantStyle {
741            bg,
742            border,
743            fg,
744            underline,
745            shadow,
746        }
747    }
748
749    fn hovered(&self, outline: bool, cx: &mut App) -> ButtonVariantStyle {
750        let bg = match self {
751            ButtonVariant::Primary => {
752                if outline {
753                    cx.theme().secondary_hover
754                } else {
755                    cx.theme().primary_hover
756                }
757            }
758            ButtonVariant::Secondary => cx.theme().secondary_hover,
759            ButtonVariant::Danger => {
760                if outline {
761                    cx.theme().secondary_hover
762                } else {
763                    cx.theme().danger_hover
764                }
765            }
766            ButtonVariant::Warning => {
767                if outline {
768                    cx.theme().secondary_hover
769                } else {
770                    cx.theme().warning_hover
771                }
772            }
773            ButtonVariant::Success => {
774                if outline {
775                    cx.theme().secondary_hover
776                } else {
777                    cx.theme().success_hover
778                }
779            }
780            ButtonVariant::Info => {
781                if outline {
782                    cx.theme().secondary_hover
783                } else {
784                    cx.theme().info_hover
785                }
786            }
787            ButtonVariant::Ghost => {
788                if cx.theme().mode.is_dark() {
789                    cx.theme().secondary.lighten(0.1).opacity(0.8)
790                } else {
791                    cx.theme().secondary.darken(0.1).opacity(0.8)
792                }
793            }
794            ButtonVariant::Link => cx.theme().transparent,
795            ButtonVariant::Text => cx.theme().transparent,
796            ButtonVariant::Custom(colors) => {
797                if outline {
798                    cx.theme().secondary_hover
799                } else {
800                    colors.hover
801                }
802            }
803        };
804
805        let border = self.border_color(bg, outline, cx);
806        let fg = match self {
807            ButtonVariant::Link => cx.theme().link_hover,
808            _ => self.text_color(outline, cx),
809        };
810
811        let underline = self.underline(cx);
812        let shadow = self.shadow(outline, cx);
813
814        ButtonVariantStyle {
815            bg,
816            border,
817            fg,
818            underline,
819            shadow,
820        }
821    }
822
823    fn active(&self, outline: bool, cx: &mut App) -> ButtonVariantStyle {
824        let bg = match self {
825            ButtonVariant::Primary => {
826                if outline {
827                    cx.theme().primary_active.opacity(0.1)
828                } else {
829                    cx.theme().primary_active
830                }
831            }
832            ButtonVariant::Secondary => cx.theme().secondary_active,
833            ButtonVariant::Ghost => {
834                if cx.theme().mode.is_dark() {
835                    cx.theme().secondary.lighten(0.2).opacity(0.8)
836                } else {
837                    cx.theme().secondary.darken(0.2).opacity(0.8)
838                }
839            }
840            ButtonVariant::Danger => {
841                if outline {
842                    cx.theme().danger_active.opacity(0.1)
843                } else {
844                    cx.theme().danger_active
845                }
846            }
847            ButtonVariant::Warning => {
848                if outline {
849                    cx.theme().warning_active.opacity(0.1)
850                } else {
851                    cx.theme().warning_active
852                }
853            }
854            ButtonVariant::Success => {
855                if outline {
856                    cx.theme().success_active.opacity(0.1)
857                } else {
858                    cx.theme().success_active
859                }
860            }
861            ButtonVariant::Info => {
862                if outline {
863                    cx.theme().info_active.opacity(0.1)
864                } else {
865                    cx.theme().info_active
866                }
867            }
868            ButtonVariant::Link => cx.theme().transparent,
869            ButtonVariant::Text => cx.theme().transparent,
870            ButtonVariant::Custom(colors) => {
871                if outline {
872                    colors.active.opacity(0.1)
873                } else {
874                    colors.active
875                }
876            }
877        };
878        let border = self.border_color(bg, outline, cx);
879        let fg = match self {
880            ButtonVariant::Link => cx.theme().link_active,
881            ButtonVariant::Text => cx.theme().foreground.opacity(0.7),
882            _ => self.text_color(outline, cx),
883        };
884        let underline = self.underline(cx);
885        let shadow = self.shadow(outline, cx);
886
887        ButtonVariantStyle {
888            bg,
889            border,
890            fg,
891            underline,
892            shadow,
893        }
894    }
895
896    fn selected(&self, outline: bool, cx: &mut App) -> ButtonVariantStyle {
897        let bg = match self {
898            ButtonVariant::Primary => cx.theme().primary_active,
899            ButtonVariant::Secondary | ButtonVariant::Ghost => cx.theme().secondary_active,
900            ButtonVariant::Danger => cx.theme().danger_active,
901            ButtonVariant::Warning => cx.theme().warning_active,
902            ButtonVariant::Success => cx.theme().success_active,
903            ButtonVariant::Info => cx.theme().info_active,
904            ButtonVariant::Link => cx.theme().transparent,
905            ButtonVariant::Text => cx.theme().transparent,
906            ButtonVariant::Custom(colors) => colors.active,
907        };
908
909        let border = self.border_color(bg, outline, cx);
910        let fg = match self {
911            ButtonVariant::Link => cx.theme().link_active,
912            ButtonVariant::Text => cx.theme().foreground.opacity(0.7),
913            _ => self.text_color(false, cx),
914        };
915        let underline = self.underline(cx);
916        let shadow = self.shadow(outline, cx);
917
918        ButtonVariantStyle {
919            bg,
920            border,
921            fg,
922            underline,
923            shadow,
924        }
925    }
926
927    fn disabled(&self, outline: bool, cx: &mut App) -> ButtonVariantStyle {
928        let bg = match self {
929            ButtonVariant::Link | ButtonVariant::Ghost | ButtonVariant::Text => {
930                cx.theme().transparent
931            }
932            ButtonVariant::Primary => cx.theme().primary.opacity(0.15),
933            ButtonVariant::Danger => cx.theme().danger.opacity(0.15),
934            ButtonVariant::Warning => cx.theme().warning.opacity(0.15),
935            ButtonVariant::Success => cx.theme().success.opacity(0.15),
936            ButtonVariant::Info => cx.theme().info.opacity(0.15),
937            ButtonVariant::Secondary => cx.theme().secondary.opacity(1.5),
938            ButtonVariant::Custom(style) => style.color.opacity(0.15),
939        };
940        let fg = cx.theme().muted_foreground.opacity(0.5);
941        let (bg, border) = if outline {
942            (cx.theme().transparent, cx.theme().border.opacity(0.5))
943        } else {
944            (bg, bg)
945        };
946
947        let underline = self.underline(cx);
948        let shadow = false;
949
950        ButtonVariantStyle {
951            bg,
952            border,
953            fg,
954            underline,
955            shadow,
956        }
957    }
958}