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 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 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 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 pub fn outline(mut self) -> Self {
247 self.outline = true;
248 self
249 }
250
251 pub fn rounded(mut self, rounded: impl Into<ButtonRounded>) -> Self {
253 self.rounded = rounded.into();
254 self
255 }
256
257 pub(crate) fn border_corners(mut self, corners: impl Into<Corners<bool>>) -> Self {
259 self.border_corners = corners.into();
260 self
261 }
262
263 pub(crate) fn border_edges(mut self, edges: impl Into<Edges<bool>>) -> Self {
265 self.border_edges = edges.into();
266 self
267 }
268
269 pub fn label(mut self, label: impl Into<SharedString>) -> Self {
271 self.label = Some(label.into());
272 self
273 }
274
275 pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
277 self.icon = Some(icon.into());
278 self
279 }
280
281 pub fn tooltip(mut self, tooltip: impl Into<SharedString>) -> Self {
283 self.tooltip = Some((tooltip.into(), None));
284 self
285 }
286
287 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 pub fn loading(mut self, loading: bool) -> Self {
306 self.loading = loading;
307 self
308 }
309
310 pub fn compact(mut self) -> Self {
312 self.compact = true;
313 self
314 }
315
316 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 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 pub fn loading_icon(mut self, icon: impl Into<Icon>) -> Self {
335 self.loading_icon = Some(icon.into());
336 self
337 }
338
339 pub fn tab_index(mut self, tab_index: isize) -> Self {
343 self.tab_index = tab_index;
344 self
345 }
346
347 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 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 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 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}