gpui_component/button/
button.rs

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