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 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 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 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(
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 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 pub fn tab_index(mut self, tab_index: isize) -> Self {
344 self.tab_index = tab_index;
345 self
346 }
347
348 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 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 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 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}