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