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 pub(crate) stop_propagation: bool,
206 loading: bool,
207 loading_icon: Option<Icon>,
208
209 tab_index: isize,
210 tab_stop: bool,
211}
212
213impl From<Button> for AnyElement {
214 fn from(button: Button) -> Self {
215 button.into_any_element()
216 }
217}
218
219impl Button {
220 pub fn new(id: impl Into<ElementId>) -> Self {
221 let id = id.into();
222
223 Self {
224 id: id.clone(),
225 base: div().flex_shrink_0().id(id),
228 style: StyleRefinement::default(),
229 icon: None,
230 label: None,
231 disabled: false,
232 selected: false,
233 variant: ButtonVariant::default(),
234 rounded: ButtonRounded::Medium,
235 border_corners: Corners::all(true),
236 border_edges: Edges::all(true),
237 size: Size::Medium,
238 tooltip: None,
239 on_click: None,
240 on_hover: None,
241 stop_propagation: true,
242 loading: false,
243 compact: false,
244 outline: false,
245 children: Vec::new(),
246 loading_icon: None,
247 tab_index: 0,
248 tab_stop: true,
249 }
250 }
251
252 pub fn outline(mut self) -> Self {
254 self.outline = true;
255 self
256 }
257
258 pub fn rounded(mut self, rounded: impl Into<ButtonRounded>) -> Self {
260 self.rounded = rounded.into();
261 self
262 }
263
264 pub(crate) fn border_corners(mut self, corners: impl Into<Corners<bool>>) -> Self {
266 self.border_corners = corners.into();
267 self
268 }
269
270 pub(crate) fn border_edges(mut self, edges: impl Into<Edges<bool>>) -> Self {
272 self.border_edges = edges.into();
273 self
274 }
275
276 pub fn label(mut self, label: impl Into<SharedString>) -> Self {
278 self.label = Some(label.into());
279 self
280 }
281
282 pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
284 self.icon = Some(icon.into());
285 self
286 }
287
288 pub fn tooltip(mut self, tooltip: impl Into<SharedString>) -> Self {
290 self.tooltip = Some((tooltip.into(), None));
291 self
292 }
293
294 pub fn tooltip_with_action(
295 mut self,
296 tooltip: impl Into<SharedString>,
297 action: &dyn Action,
298 context: Option<&str>,
299 ) -> Self {
300 self.tooltip = Some((
301 tooltip.into(),
302 Some((
303 Rc::new(action.boxed_clone()),
304 context.map(|c| c.to_string().into()),
305 )),
306 ));
307 self
308 }
309
310 pub fn loading(mut self, loading: bool) -> Self {
312 self.loading = loading;
313 self
314 }
315
316 pub fn compact(mut self) -> Self {
318 self.compact = true;
319 self
320 }
321
322 pub fn on_click(
324 mut self,
325 handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
326 ) -> Self {
327 self.on_click = Some(Rc::new(handler));
328 self
329 }
330
331 pub fn on_hover(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self {
333 self.on_hover = Some(Rc::new(handler));
334 self
335 }
336
337 pub fn stop_propagation(mut self, val: bool) -> Self {
338 self.stop_propagation = val;
339 self
340 }
341
342 pub fn loading_icon(mut self, icon: impl Into<Icon>) -> Self {
343 self.loading_icon = Some(icon.into());
344 self
345 }
346
347 pub fn tab_index(mut self, tab_index: isize) -> Self {
351 self.tab_index = tab_index;
352 self
353 }
354
355 pub fn tab_stop(mut self, tab_stop: bool) -> Self {
359 self.tab_stop = tab_stop;
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 hoverable = self.hoverable();
429 let normal_style = style.normal(self.outline, cx);
430 let icon_size = match self.size {
431 Size::Size(v) => Size::Size(v * 0.75),
432 _ => self.size,
433 };
434
435 let focus_handle = window
436 .use_keyed_state(self.id.clone(), cx, |_, cx| cx.focus_handle())
437 .read(cx)
438 .clone();
439 let is_focused = focus_handle.is_focused(window);
440
441 self.base
442 .when(!self.disabled, |this| {
443 this.track_focus(
444 &focus_handle
445 .tab_index(self.tab_index)
446 .tab_stop(self.tab_stop),
447 )
448 })
449 .cursor_default()
450 .flex()
451 .flex_shrink_0()
452 .items_center()
453 .justify_center()
454 .cursor_default()
455 .when(self.variant.is_link(), |this| this.cursor_pointer())
456 .when(cx.theme().shadow && normal_style.shadow, |this| {
457 this.shadow_xs()
458 })
459 .when(!style.no_padding(), |this| {
460 if self.label.is_none() && self.children.is_empty() {
461 match self.size {
463 Size::Size(px) => this.size(px),
464 Size::XSmall => this.size_5(),
465 Size::Small => this.size_6(),
466 Size::Large | Size::Medium => this.size_8(),
467 }
468 } else {
469 match self.size {
471 Size::Size(size) => this.px(size * 0.2),
472 Size::XSmall => this.h_5().px_1(),
473 Size::Small => this.h_6().px_3().when(self.compact, |this| this.px_1p5()),
474 _ => this.h_8().px_4().when(self.compact, |this| this.px_2()),
475 }
476 }
477 })
478 .when(
479 self.border_corners.top_left && self.border_corners.bottom_left,
480 |this| match self.rounded {
481 ButtonRounded::Small => this.rounded_l(cx.theme().radius * 0.5),
482 ButtonRounded::Medium => this.rounded_l(cx.theme().radius),
483 ButtonRounded::Large => this.rounded_l(cx.theme().radius * 2.0),
484 ButtonRounded::Size(px) => this.rounded_l(px),
485 ButtonRounded::None => this.rounded_none(),
486 },
487 )
488 .when(
489 self.border_corners.top_right && self.border_corners.bottom_right,
490 |this| match self.rounded {
491 ButtonRounded::Small => this.rounded_r(cx.theme().radius * 0.5),
492 ButtonRounded::Medium => this.rounded_r(cx.theme().radius),
493 ButtonRounded::Large => this.rounded_r(cx.theme().radius * 2.0),
494 ButtonRounded::Size(px) => this.rounded_r(px),
495 ButtonRounded::None => this.rounded_none(),
496 },
497 )
498 .when(self.border_edges.left, |this| this.border_l_1())
499 .when(self.border_edges.right, |this| this.border_r_1())
500 .when(self.border_edges.top, |this| this.border_t_1())
501 .when(self.border_edges.bottom, |this| this.border_b_1())
502 .text_color(normal_style.fg)
503 .when(self.selected, |this| {
504 let selected_style = style.selected(self.outline, cx);
505 this.bg(selected_style.bg)
506 .border_color(selected_style.border)
507 .text_color(selected_style.fg)
508 })
509 .when(!self.disabled && !self.selected, |this| {
510 this.border_color(normal_style.border)
511 .bg(normal_style.bg)
512 .when(normal_style.underline, |this| this.text_decoration_1())
513 .hover(|this| {
514 let hover_style = style.hovered(self.outline, cx);
515 this.bg(hover_style.bg)
516 .border_color(hover_style.border)
517 .text_color(crate::red_400())
518 })
519 .active(|this| {
520 let active_style = style.active(self.outline, cx);
521 this.bg(active_style.bg)
522 .border_color(active_style.border)
523 .text_color(active_style.fg)
524 })
525 })
526 .when(self.disabled, |this| {
527 let disabled_style = style.disabled(self.outline, cx);
528 this.bg(disabled_style.bg)
529 .text_color(disabled_style.fg)
530 .border_color(disabled_style.border)
531 .shadow_none()
532 })
533 .refine_style(&self.style)
534 .on_mouse_down(gpui::MouseButton::Left, |_, window, _| {
535 window.prevent_default();
537 })
538 .when_some(self.on_click.filter(|_| clickable), |this, on_click| {
539 let stop_propagation = self.stop_propagation;
540 this.on_click(move |_, _, cx| {
541 if stop_propagation {
542 cx.stop_propagation();
543 }
544 })
545 .on_click(move |event, window, cx| {
546 (on_click)(event, window, cx);
547 })
548 })
549 .when_some(self.on_hover.filter(|_| hoverable), |this, on_hover| {
550 this.on_hover(move |hovered, window, cx| {
551 (on_hover)(hovered, window, cx);
552 })
553 })
554 .when(self.disabled, |this| {
555 let disabled_style = style.disabled(self.outline, cx);
556 this.bg(disabled_style.bg)
557 .text_color(disabled_style.fg)
558 .border_color(disabled_style.border)
559 .shadow_none()
560 })
561 .child({
562 h_flex()
563 .id("label")
564 .items_center()
565 .justify_center()
566 .button_text_size(self.size)
567 .map(|this| match self.size {
568 Size::XSmall => this.gap_1(),
569 Size::Small => this.gap_1(),
570 _ => this.gap_2(),
571 })
572 .when(!self.loading, |this| {
573 this.when_some(self.icon, |this, icon| {
574 this.child(icon.with_size(icon_size))
575 })
576 })
577 .when(self.loading, |this| {
578 this.child(
579 Indicator::new()
580 .with_size(self.size)
581 .when_some(self.loading_icon, |this, icon| this.icon(icon)),
582 )
583 })
584 .when_some(self.label, |this, label| {
585 this.child(div().flex_none().line_height(relative(1.)).child(label))
586 })
587 .children(self.children)
588 })
589 .when(self.loading && !self.disabled, |this| {
590 this.bg(normal_style.bg.opacity(0.8))
591 .border_color(normal_style.border.opacity(0.8))
592 .text_color(normal_style.fg.opacity(0.8))
593 })
594 .when_some(self.tooltip, |this, (tooltip, action)| {
595 this.tooltip(move |window, cx| {
596 Tooltip::new(tooltip.clone())
597 .when_some(action.clone(), |this, (action, context)| {
598 this.action(
599 action.boxed_clone().as_ref(),
600 context.as_ref().map(|c| c.as_ref()),
601 )
602 })
603 .build(window, cx)
604 })
605 })
606 .focus_ring(is_focused, px(0.), window, cx)
607 }
608}
609
610struct ButtonVariantStyle {
611 bg: Hsla,
612 border: Hsla,
613 fg: Hsla,
614 underline: bool,
615 shadow: bool,
616}
617
618impl ButtonVariant {
619 fn bg_color(&self, outline: bool, cx: &mut App) -> Hsla {
620 if outline {
621 return cx.theme().background;
622 }
623
624 match self {
625 ButtonVariant::Primary => cx.theme().primary,
626 ButtonVariant::Secondary => cx.theme().secondary,
627 ButtonVariant::Danger => cx.theme().danger,
628 ButtonVariant::Warning => cx.theme().warning,
629 ButtonVariant::Success => cx.theme().success,
630 ButtonVariant::Info => cx.theme().info,
631 ButtonVariant::Ghost | ButtonVariant::Link | ButtonVariant::Text => {
632 cx.theme().transparent
633 }
634 ButtonVariant::Custom(colors) => colors.color,
635 }
636 }
637
638 fn text_color(&self, outline: bool, cx: &mut App) -> Hsla {
639 match self {
640 ButtonVariant::Primary => {
641 if outline {
642 cx.theme().primary
643 } else {
644 cx.theme().primary_foreground
645 }
646 }
647 ButtonVariant::Secondary | ButtonVariant::Ghost => cx.theme().secondary_foreground,
648 ButtonVariant::Danger => {
649 if outline {
650 cx.theme().danger
651 } else {
652 cx.theme().danger_foreground
653 }
654 }
655 ButtonVariant::Warning => {
656 if outline {
657 cx.theme().warning
658 } else {
659 cx.theme().warning_foreground
660 }
661 }
662 ButtonVariant::Success => {
663 if outline {
664 cx.theme().success
665 } else {
666 cx.theme().success_foreground
667 }
668 }
669 ButtonVariant::Info => {
670 if outline {
671 cx.theme().info
672 } else {
673 cx.theme().info_foreground
674 }
675 }
676 ButtonVariant::Link => cx.theme().link,
677 ButtonVariant::Text => cx.theme().foreground,
678 ButtonVariant::Custom(colors) => {
679 if outline {
680 colors.color
681 } else {
682 colors.foreground
683 }
684 }
685 }
686 }
687
688 fn border_color(&self, bg: Hsla, outline: bool, cx: &mut App) -> Hsla {
689 match self {
690 ButtonVariant::Secondary => {
691 if outline {
692 cx.theme().border
693 } else {
694 bg
695 }
696 }
697 ButtonVariant::Primary => {
698 if outline {
699 cx.theme().primary
700 } else {
701 bg
702 }
703 }
704 ButtonVariant::Danger => {
705 if outline {
706 cx.theme().danger
707 } else {
708 bg
709 }
710 }
711 ButtonVariant::Info => {
712 if outline {
713 cx.theme().info
714 } else {
715 bg
716 }
717 }
718 ButtonVariant::Warning => {
719 if outline {
720 cx.theme().warning
721 } else {
722 bg
723 }
724 }
725 ButtonVariant::Success => {
726 if outline {
727 cx.theme().success
728 } else {
729 bg
730 }
731 }
732 ButtonVariant::Ghost | ButtonVariant::Link | ButtonVariant::Text => {
733 cx.theme().transparent
734 }
735 ButtonVariant::Custom(colors) => colors.border,
736 }
737 }
738
739 fn underline(&self, _: &App) -> bool {
740 match self {
741 ButtonVariant::Link => true,
742 _ => false,
743 }
744 }
745
746 fn shadow(&self, outline: bool, _: &App) -> bool {
747 match self {
748 ButtonVariant::Primary | ButtonVariant::Secondary | ButtonVariant::Danger => outline,
749 ButtonVariant::Custom(c) => c.shadow,
750 _ => false,
751 }
752 }
753
754 fn normal(&self, outline: bool, cx: &mut App) -> ButtonVariantStyle {
755 let bg = self.bg_color(outline, cx);
756 let border = self.border_color(bg, outline, cx);
757 let fg = self.text_color(outline, cx);
758 let underline = self.underline(cx);
759 let shadow = self.shadow(outline, cx);
760
761 ButtonVariantStyle {
762 bg,
763 border,
764 fg,
765 underline,
766 shadow,
767 }
768 }
769
770 fn hovered(&self, outline: bool, cx: &mut App) -> ButtonVariantStyle {
771 let bg = match self {
772 ButtonVariant::Primary => {
773 if outline {
774 cx.theme().secondary_hover
775 } else {
776 cx.theme().primary_hover
777 }
778 }
779 ButtonVariant::Secondary => cx.theme().secondary_hover,
780 ButtonVariant::Danger => {
781 if outline {
782 cx.theme().secondary_hover
783 } else {
784 cx.theme().danger_hover
785 }
786 }
787 ButtonVariant::Warning => {
788 if outline {
789 cx.theme().secondary_hover
790 } else {
791 cx.theme().warning_hover
792 }
793 }
794 ButtonVariant::Success => {
795 if outline {
796 cx.theme().secondary_hover
797 } else {
798 cx.theme().success_hover
799 }
800 }
801 ButtonVariant::Info => {
802 if outline {
803 cx.theme().secondary_hover
804 } else {
805 cx.theme().info_hover
806 }
807 }
808 ButtonVariant::Ghost => {
809 if cx.theme().mode.is_dark() {
810 cx.theme().secondary.lighten(0.1).opacity(0.8)
811 } else {
812 cx.theme().secondary.darken(0.1).opacity(0.8)
813 }
814 }
815 ButtonVariant::Link => cx.theme().transparent,
816 ButtonVariant::Text => cx.theme().transparent,
817 ButtonVariant::Custom(colors) => {
818 if outline {
819 cx.theme().secondary_hover
820 } else {
821 colors.hover
822 }
823 }
824 };
825
826 let border = self.border_color(bg, outline, cx);
827 let fg = match self {
828 ButtonVariant::Link => cx.theme().link_hover,
829 _ => self.text_color(outline, cx),
830 };
831
832 let underline = self.underline(cx);
833 let shadow = self.shadow(outline, cx);
834
835 ButtonVariantStyle {
836 bg,
837 border,
838 fg,
839 underline,
840 shadow,
841 }
842 }
843
844 fn active(&self, outline: bool, cx: &mut App) -> ButtonVariantStyle {
845 let bg = match self {
846 ButtonVariant::Primary => {
847 if outline {
848 cx.theme().primary_active.opacity(0.1)
849 } else {
850 cx.theme().primary_active
851 }
852 }
853 ButtonVariant::Secondary => cx.theme().secondary_active,
854 ButtonVariant::Ghost => {
855 if cx.theme().mode.is_dark() {
856 cx.theme().secondary.lighten(0.2).opacity(0.8)
857 } else {
858 cx.theme().secondary.darken(0.2).opacity(0.8)
859 }
860 }
861 ButtonVariant::Danger => {
862 if outline {
863 cx.theme().danger_active.opacity(0.1)
864 } else {
865 cx.theme().danger_active
866 }
867 }
868 ButtonVariant::Warning => {
869 if outline {
870 cx.theme().warning_active.opacity(0.1)
871 } else {
872 cx.theme().warning_active
873 }
874 }
875 ButtonVariant::Success => {
876 if outline {
877 cx.theme().success_active.opacity(0.1)
878 } else {
879 cx.theme().success_active
880 }
881 }
882 ButtonVariant::Info => {
883 if outline {
884 cx.theme().info_active.opacity(0.1)
885 } else {
886 cx.theme().info_active
887 }
888 }
889 ButtonVariant::Link => cx.theme().transparent,
890 ButtonVariant::Text => cx.theme().transparent,
891 ButtonVariant::Custom(colors) => {
892 if outline {
893 colors.active.opacity(0.1)
894 } else {
895 colors.active
896 }
897 }
898 };
899 let border = self.border_color(bg, outline, cx);
900 let fg = match self {
901 ButtonVariant::Link => cx.theme().link_active,
902 ButtonVariant::Text => cx.theme().foreground.opacity(0.7),
903 _ => self.text_color(outline, cx),
904 };
905 let underline = self.underline(cx);
906 let shadow = self.shadow(outline, cx);
907
908 ButtonVariantStyle {
909 bg,
910 border,
911 fg,
912 underline,
913 shadow,
914 }
915 }
916
917 fn selected(&self, outline: bool, cx: &mut App) -> ButtonVariantStyle {
918 let bg = match self {
919 ButtonVariant::Primary => cx.theme().primary_active,
920 ButtonVariant::Secondary | ButtonVariant::Ghost => cx.theme().secondary_active,
921 ButtonVariant::Danger => cx.theme().danger_active,
922 ButtonVariant::Warning => cx.theme().warning_active,
923 ButtonVariant::Success => cx.theme().success_active,
924 ButtonVariant::Info => cx.theme().info_active,
925 ButtonVariant::Link => cx.theme().transparent,
926 ButtonVariant::Text => cx.theme().transparent,
927 ButtonVariant::Custom(colors) => colors.active,
928 };
929
930 let border = self.border_color(bg, outline, cx);
931 let fg = match self {
932 ButtonVariant::Link => cx.theme().link_active,
933 ButtonVariant::Text => cx.theme().foreground.opacity(0.7),
934 _ => self.text_color(false, cx),
935 };
936 let underline = self.underline(cx);
937 let shadow = self.shadow(outline, cx);
938
939 ButtonVariantStyle {
940 bg,
941 border,
942 fg,
943 underline,
944 shadow,
945 }
946 }
947
948 fn disabled(&self, outline: bool, cx: &mut App) -> ButtonVariantStyle {
949 let bg = match self {
950 ButtonVariant::Link | ButtonVariant::Ghost | ButtonVariant::Text => {
951 cx.theme().transparent
952 }
953 ButtonVariant::Primary => cx.theme().primary.opacity(0.15),
954 ButtonVariant::Danger => cx.theme().danger.opacity(0.15),
955 ButtonVariant::Warning => cx.theme().warning.opacity(0.15),
956 ButtonVariant::Success => cx.theme().success.opacity(0.15),
957 ButtonVariant::Info => cx.theme().info.opacity(0.15),
958 ButtonVariant::Secondary => cx.theme().secondary.opacity(1.5),
959 ButtonVariant::Custom(style) => style.color.opacity(0.15),
960 };
961 let fg = cx.theme().muted_foreground.opacity(0.5);
962 let (bg, border) = if outline {
963 (cx.theme().transparent, cx.theme().border.opacity(0.5))
964 } else {
965 (bg, bg)
966 };
967
968 let underline = self.underline(cx);
969 let shadow = false;
970
971 ButtonVariantStyle {
972 bg,
973 border,
974 fg,
975 underline,
976 shadow,
977 }
978 }
979}