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 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)]
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#[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 pub(crate) stop_propagation: bool,
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 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 stop_propagation: true,
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 pub fn outline(mut self) -> Self {
252 self.outline = true;
253 self
254 }
255
256 pub fn rounded(mut self, rounded: impl Into<ButtonRounded>) -> Self {
258 self.rounded = rounded.into();
259 self
260 }
261
262 pub(crate) fn border_corners(mut self, corners: impl Into<Corners<bool>>) -> Self {
264 self.border_corners = corners.into();
265 self
266 }
267
268 pub(crate) fn border_edges(mut self, edges: impl Into<Edges<bool>>) -> Self {
270 self.border_edges = edges.into();
271 self
272 }
273
274 pub fn label(mut self, label: impl Into<SharedString>) -> Self {
276 self.label = Some(label.into());
277 self
278 }
279
280 pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
282 self.icon = Some(icon.into());
283 self
284 }
285
286 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 pub fn loading(mut self, loading: bool) -> Self {
310 self.loading = loading;
311 self
312 }
313
314 pub fn compact(mut self) -> Self {
316 self.compact = true;
317 self
318 }
319
320 pub fn on_click(
321 mut self,
322 handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
323 ) -> Self {
324 self.on_click = Some(Rc::new(handler));
325 self
326 }
327
328 pub fn stop_propagation(mut self, val: bool) -> Self {
329 self.stop_propagation = val;
330 self
331 }
332
333 pub fn loading_icon(mut self, icon: impl Into<Icon>) -> Self {
334 self.loading_icon = Some(icon.into());
335 self
336 }
337
338 pub fn tab_index(mut self, tab_index: isize) -> Self {
342 self.tab_index = tab_index;
343 self
344 }
345
346 pub fn tab_stop(mut self, tab_stop: bool) -> Self {
350 self.tab_stop = tab_stop;
351 self
352 }
353}
354
355impl Disableable for Button {
356 fn disabled(mut self, disabled: bool) -> Self {
357 self.disabled = disabled;
358 self
359 }
360}
361
362impl Selectable for Button {
363 fn selected(mut self, selected: bool) -> Self {
364 self.selected = selected;
365 self
366 }
367
368 fn is_selected(&self) -> bool {
369 self.selected
370 }
371}
372
373impl Sizable for Button {
374 fn with_size(mut self, size: impl Into<Size>) -> Self {
375 self.size = size.into();
376 self
377 }
378}
379
380impl ButtonVariants for Button {
381 fn with_variant(mut self, variant: ButtonVariant) -> Self {
382 self.variant = variant;
383 self
384 }
385}
386
387impl Styled for Button {
388 fn style(&mut self) -> &mut StyleRefinement {
389 &mut self.style
390 }
391}
392
393impl ParentElement for Button {
394 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
395 self.children.extend(elements)
396 }
397}
398
399impl InteractiveElement for Button {
400 fn interactivity(&mut self) -> &mut Interactivity {
401 self.base.interactivity()
402 }
403}
404
405impl RenderOnce for Button {
406 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
407 let style: ButtonVariant = self.variant;
408 let clickable = !(self.disabled || self.loading) && self.on_click.is_some();
409 let normal_style = style.normal(self.outline, cx);
410 let icon_size = match self.size {
411 Size::Size(v) => Size::Size(v * 0.75),
412 _ => self.size,
413 };
414
415 let focus_handle = window
416 .use_keyed_state(self.id.clone(), cx, |_, cx| cx.focus_handle())
417 .read(cx)
418 .clone();
419 let is_focused = focus_handle.is_focused(window);
420
421 self.base
422 .when(!self.disabled, |this| {
423 this.track_focus(
424 &focus_handle
425 .tab_index(self.tab_index)
426 .tab_stop(self.tab_stop),
427 )
428 })
429 .cursor_default()
430 .flex()
431 .flex_shrink_0()
432 .items_center()
433 .justify_center()
434 .cursor_default()
435 .when(self.variant.is_link(), |this| this.cursor_pointer())
436 .when(cx.theme().shadow && normal_style.shadow, |this| {
437 this.shadow_xs()
438 })
439 .when(!style.no_padding(), |this| {
440 if self.label.is_none() && self.children.is_empty() {
441 match self.size {
443 Size::Size(px) => this.size(px),
444 Size::XSmall => this.size_5(),
445 Size::Small => this.size_6(),
446 Size::Large | Size::Medium => this.size_8(),
447 }
448 } else {
449 match self.size {
451 Size::Size(size) => this.px(size * 0.2),
452 Size::XSmall => this.h_5().px_1(),
453 Size::Small => this.h_6().px_3().when(self.compact, |this| this.px_1p5()),
454 _ => this.h_8().px_4().when(self.compact, |this| this.px_2()),
455 }
456 }
457 })
458 .when(
459 self.border_corners.top_left && self.border_corners.bottom_left,
460 |this| match self.rounded {
461 ButtonRounded::Small => this.rounded_l(cx.theme().radius * 0.5),
462 ButtonRounded::Medium => this.rounded_l(cx.theme().radius),
463 ButtonRounded::Large => this.rounded_l(cx.theme().radius * 2.0),
464 ButtonRounded::Size(px) => this.rounded_l(px),
465 ButtonRounded::None => this.rounded_none(),
466 },
467 )
468 .when(
469 self.border_corners.top_right && self.border_corners.bottom_right,
470 |this| match self.rounded {
471 ButtonRounded::Small => this.rounded_r(cx.theme().radius * 0.5),
472 ButtonRounded::Medium => this.rounded_r(cx.theme().radius),
473 ButtonRounded::Large => this.rounded_r(cx.theme().radius * 2.0),
474 ButtonRounded::Size(px) => this.rounded_r(px),
475 ButtonRounded::None => this.rounded_none(),
476 },
477 )
478 .when(self.border_edges.left, |this| this.border_l_1())
479 .when(self.border_edges.right, |this| this.border_r_1())
480 .when(self.border_edges.top, |this| this.border_t_1())
481 .when(self.border_edges.bottom, |this| this.border_b_1())
482 .text_color(normal_style.fg)
483 .when(self.selected, |this| {
484 let selected_style = style.selected(self.outline, cx);
485 this.bg(selected_style.bg)
486 .border_color(selected_style.border)
487 .text_color(selected_style.fg)
488 })
489 .when(!self.disabled && !self.selected, |this| {
490 this.border_color(normal_style.border)
491 .bg(normal_style.bg)
492 .when(normal_style.underline, |this| this.text_decoration_1())
493 .hover(|this| {
494 let hover_style = style.hovered(self.outline, cx);
495 this.bg(hover_style.bg)
496 .border_color(hover_style.border)
497 .text_color(crate::red_400())
498 })
499 .active(|this| {
500 let active_style = style.active(self.outline, cx);
501 this.bg(active_style.bg)
502 .border_color(active_style.border)
503 .text_color(active_style.fg)
504 })
505 })
506 .when(self.disabled, |this| {
507 let disabled_style = style.disabled(self.outline, cx);
508 this.bg(disabled_style.bg)
509 .text_color(disabled_style.fg)
510 .border_color(disabled_style.border)
511 .shadow_none()
512 })
513 .refine_style(&self.style)
514 .on_mouse_down(gpui::MouseButton::Left, |_, window, _| {
515 window.prevent_default();
517 })
518 .when_some(self.on_click.filter(|_| clickable), |this, on_click| {
519 let stop_propagation = self.stop_propagation;
520 this.on_click(move |_, _, cx| {
521 if stop_propagation {
522 cx.stop_propagation();
523 }
524 })
525 .on_click(move |event, window, cx| {
526 (on_click)(event, window, cx);
527 })
528 })
529 .when(self.disabled, |this| {
530 let disabled_style = style.disabled(self.outline, cx);
531 this.bg(disabled_style.bg)
532 .text_color(disabled_style.fg)
533 .border_color(disabled_style.border)
534 .shadow_none()
535 })
536 .child({
537 h_flex()
538 .id("label")
539 .items_center()
540 .justify_center()
541 .button_text_size(self.size)
542 .map(|this| match self.size {
543 Size::XSmall => this.gap_1(),
544 Size::Small => this.gap_1(),
545 _ => this.gap_2(),
546 })
547 .when(!self.loading, |this| {
548 this.when_some(self.icon, |this, icon| {
549 this.child(icon.with_size(icon_size))
550 })
551 })
552 .when(self.loading, |this| {
553 this.child(
554 Indicator::new()
555 .with_size(self.size)
556 .when_some(self.loading_icon, |this, icon| this.icon(icon)),
557 )
558 })
559 .when_some(self.label, |this, label| {
560 this.child(div().flex_none().line_height(relative(1.)).child(label))
561 })
562 .children(self.children)
563 })
564 .when(self.loading && !self.disabled, |this| {
565 this.bg(normal_style.bg.opacity(0.8))
566 .border_color(normal_style.border.opacity(0.8))
567 .text_color(normal_style.fg.opacity(0.8))
568 })
569 .when_some(self.tooltip, |this, (tooltip, action)| {
570 this.tooltip(move |window, cx| {
571 Tooltip::new(tooltip.clone())
572 .when_some(action.clone(), |this, (action, context)| {
573 this.action(
574 action.boxed_clone().as_ref(),
575 context.as_ref().map(|c| c.as_ref()),
576 )
577 })
578 .build(window, cx)
579 })
580 })
581 .focus_ring(is_focused, px(0.), window, cx)
582 }
583}
584
585struct ButtonVariantStyle {
586 bg: Hsla,
587 border: Hsla,
588 fg: Hsla,
589 underline: bool,
590 shadow: bool,
591}
592
593impl ButtonVariant {
594 fn bg_color(&self, outline: bool, cx: &mut App) -> Hsla {
595 if outline {
596 return cx.theme().background;
597 }
598
599 match self {
600 ButtonVariant::Primary => cx.theme().primary,
601 ButtonVariant::Secondary => cx.theme().secondary,
602 ButtonVariant::Danger => cx.theme().danger,
603 ButtonVariant::Warning => cx.theme().warning,
604 ButtonVariant::Success => cx.theme().success,
605 ButtonVariant::Info => cx.theme().info,
606 ButtonVariant::Ghost | ButtonVariant::Link | ButtonVariant::Text => {
607 cx.theme().transparent
608 }
609 ButtonVariant::Custom(colors) => colors.color,
610 }
611 }
612
613 fn text_color(&self, outline: bool, cx: &mut App) -> Hsla {
614 match self {
615 ButtonVariant::Primary => {
616 if outline {
617 cx.theme().primary
618 } else {
619 cx.theme().primary_foreground
620 }
621 }
622 ButtonVariant::Secondary | ButtonVariant::Ghost => cx.theme().secondary_foreground,
623 ButtonVariant::Danger => {
624 if outline {
625 cx.theme().danger
626 } else {
627 cx.theme().danger_foreground
628 }
629 }
630 ButtonVariant::Warning => {
631 if outline {
632 cx.theme().warning
633 } else {
634 cx.theme().warning_foreground
635 }
636 }
637 ButtonVariant::Success => {
638 if outline {
639 cx.theme().success
640 } else {
641 cx.theme().success_foreground
642 }
643 }
644 ButtonVariant::Info => {
645 if outline {
646 cx.theme().info
647 } else {
648 cx.theme().info_foreground
649 }
650 }
651 ButtonVariant::Link => cx.theme().link,
652 ButtonVariant::Text => cx.theme().foreground,
653 ButtonVariant::Custom(colors) => {
654 if outline {
655 colors.color
656 } else {
657 colors.foreground
658 }
659 }
660 }
661 }
662
663 fn border_color(&self, bg: Hsla, outline: bool, cx: &mut App) -> Hsla {
664 match self {
665 ButtonVariant::Secondary => {
666 if outline {
667 cx.theme().border
668 } else {
669 bg
670 }
671 }
672 ButtonVariant::Primary => {
673 if outline {
674 cx.theme().primary
675 } else {
676 bg
677 }
678 }
679 ButtonVariant::Danger => {
680 if outline {
681 cx.theme().danger
682 } else {
683 bg
684 }
685 }
686 ButtonVariant::Info => {
687 if outline {
688 cx.theme().info
689 } else {
690 bg
691 }
692 }
693 ButtonVariant::Warning => {
694 if outline {
695 cx.theme().warning
696 } else {
697 bg
698 }
699 }
700 ButtonVariant::Success => {
701 if outline {
702 cx.theme().success
703 } else {
704 bg
705 }
706 }
707 ButtonVariant::Ghost | ButtonVariant::Link | ButtonVariant::Text => {
708 cx.theme().transparent
709 }
710 ButtonVariant::Custom(colors) => colors.border,
711 }
712 }
713
714 fn underline(&self, _: &App) -> bool {
715 match self {
716 ButtonVariant::Link => true,
717 _ => false,
718 }
719 }
720
721 fn shadow(&self, outline: bool, _: &App) -> bool {
722 match self {
723 ButtonVariant::Primary | ButtonVariant::Secondary | ButtonVariant::Danger => outline,
724 ButtonVariant::Custom(c) => c.shadow,
725 _ => false,
726 }
727 }
728
729 fn normal(&self, outline: bool, cx: &mut App) -> ButtonVariantStyle {
730 let bg = self.bg_color(outline, cx);
731 let border = self.border_color(bg, outline, cx);
732 let fg = self.text_color(outline, cx);
733 let underline = self.underline(cx);
734 let shadow = self.shadow(outline, cx);
735
736 ButtonVariantStyle {
737 bg,
738 border,
739 fg,
740 underline,
741 shadow,
742 }
743 }
744
745 fn hovered(&self, outline: bool, cx: &mut App) -> ButtonVariantStyle {
746 let bg = match self {
747 ButtonVariant::Primary => {
748 if outline {
749 cx.theme().secondary_hover
750 } else {
751 cx.theme().primary_hover
752 }
753 }
754 ButtonVariant::Secondary => cx.theme().secondary_hover,
755 ButtonVariant::Danger => {
756 if outline {
757 cx.theme().secondary_hover
758 } else {
759 cx.theme().danger_hover
760 }
761 }
762 ButtonVariant::Warning => {
763 if outline {
764 cx.theme().secondary_hover
765 } else {
766 cx.theme().warning_hover
767 }
768 }
769 ButtonVariant::Success => {
770 if outline {
771 cx.theme().secondary_hover
772 } else {
773 cx.theme().success_hover
774 }
775 }
776 ButtonVariant::Info => {
777 if outline {
778 cx.theme().secondary_hover
779 } else {
780 cx.theme().info_hover
781 }
782 }
783 ButtonVariant::Ghost => {
784 if cx.theme().mode.is_dark() {
785 cx.theme().secondary.lighten(0.1).opacity(0.8)
786 } else {
787 cx.theme().secondary.darken(0.1).opacity(0.8)
788 }
789 }
790 ButtonVariant::Link => cx.theme().transparent,
791 ButtonVariant::Text => cx.theme().transparent,
792 ButtonVariant::Custom(colors) => {
793 if outline {
794 cx.theme().secondary_hover
795 } else {
796 colors.hover
797 }
798 }
799 };
800
801 let border = self.border_color(bg, outline, cx);
802 let fg = match self {
803 ButtonVariant::Link => cx.theme().link_hover,
804 _ => self.text_color(outline, cx),
805 };
806
807 let underline = self.underline(cx);
808 let shadow = self.shadow(outline, cx);
809
810 ButtonVariantStyle {
811 bg,
812 border,
813 fg,
814 underline,
815 shadow,
816 }
817 }
818
819 fn active(&self, outline: bool, cx: &mut App) -> ButtonVariantStyle {
820 let bg = match self {
821 ButtonVariant::Primary => {
822 if outline {
823 cx.theme().primary_active.opacity(0.1)
824 } else {
825 cx.theme().primary_active
826 }
827 }
828 ButtonVariant::Secondary => cx.theme().secondary_active,
829 ButtonVariant::Ghost => {
830 if cx.theme().mode.is_dark() {
831 cx.theme().secondary.lighten(0.2).opacity(0.8)
832 } else {
833 cx.theme().secondary.darken(0.2).opacity(0.8)
834 }
835 }
836 ButtonVariant::Danger => {
837 if outline {
838 cx.theme().danger_active.opacity(0.1)
839 } else {
840 cx.theme().danger_active
841 }
842 }
843 ButtonVariant::Warning => {
844 if outline {
845 cx.theme().warning_active.opacity(0.1)
846 } else {
847 cx.theme().warning_active
848 }
849 }
850 ButtonVariant::Success => {
851 if outline {
852 cx.theme().success_active.opacity(0.1)
853 } else {
854 cx.theme().success_active
855 }
856 }
857 ButtonVariant::Info => {
858 if outline {
859 cx.theme().info_active.opacity(0.1)
860 } else {
861 cx.theme().info_active
862 }
863 }
864 ButtonVariant::Link => cx.theme().transparent,
865 ButtonVariant::Text => cx.theme().transparent,
866 ButtonVariant::Custom(colors) => {
867 if outline {
868 colors.active.opacity(0.1)
869 } else {
870 colors.active
871 }
872 }
873 };
874 let border = self.border_color(bg, outline, cx);
875 let fg = match self {
876 ButtonVariant::Link => cx.theme().link_active,
877 ButtonVariant::Text => cx.theme().foreground.opacity(0.7),
878 _ => self.text_color(outline, cx),
879 };
880 let underline = self.underline(cx);
881 let shadow = self.shadow(outline, cx);
882
883 ButtonVariantStyle {
884 bg,
885 border,
886 fg,
887 underline,
888 shadow,
889 }
890 }
891
892 fn selected(&self, outline: bool, cx: &mut App) -> ButtonVariantStyle {
893 let bg = match self {
894 ButtonVariant::Primary => cx.theme().primary_active,
895 ButtonVariant::Secondary | ButtonVariant::Ghost => cx.theme().secondary_active,
896 ButtonVariant::Danger => cx.theme().danger_active,
897 ButtonVariant::Warning => cx.theme().warning_active,
898 ButtonVariant::Success => cx.theme().success_active,
899 ButtonVariant::Info => cx.theme().info_active,
900 ButtonVariant::Link => cx.theme().transparent,
901 ButtonVariant::Text => cx.theme().transparent,
902 ButtonVariant::Custom(colors) => colors.active,
903 };
904
905 let border = self.border_color(bg, outline, cx);
906 let fg = match self {
907 ButtonVariant::Link => cx.theme().link_active,
908 ButtonVariant::Text => cx.theme().foreground.opacity(0.7),
909 _ => self.text_color(false, cx),
910 };
911 let underline = self.underline(cx);
912 let shadow = self.shadow(outline, cx);
913
914 ButtonVariantStyle {
915 bg,
916 border,
917 fg,
918 underline,
919 shadow,
920 }
921 }
922
923 fn disabled(&self, outline: bool, cx: &mut App) -> ButtonVariantStyle {
924 let bg = match self {
925 ButtonVariant::Link | ButtonVariant::Ghost | ButtonVariant::Text => {
926 cx.theme().transparent
927 }
928 ButtonVariant::Primary => cx.theme().primary.opacity(0.15),
929 ButtonVariant::Danger => cx.theme().danger.opacity(0.15),
930 ButtonVariant::Warning => cx.theme().warning.opacity(0.15),
931 ButtonVariant::Success => cx.theme().success.opacity(0.15),
932 ButtonVariant::Info => cx.theme().info.opacity(0.15),
933 ButtonVariant::Secondary => cx.theme().secondary.opacity(1.5),
934 ButtonVariant::Custom(style) => style.color.opacity(0.15),
935 };
936 let fg = cx.theme().muted_foreground.opacity(0.5);
937 let (bg, border) = if outline {
938 (cx.theme().transparent, cx.theme().border.opacity(0.5))
939 } else {
940 (bg, bg)
941 };
942
943 let underline = self.underline(cx);
944 let shadow = false;
945
946 ButtonVariantStyle {
947 bg,
948 border,
949 fg,
950 underline,
951 shadow,
952 }
953 }
954}