Skip to main content

guise/
button.rs

1//! `Button` — the flagship interactive control, with Mantine's variants,
2//! colors, and sizes.
3
4use gpui::prelude::*;
5use gpui::{div, px, App, ClickEvent, ElementId, FontWeight, IntoElement, SharedString, Window};
6
7use crate::input::ClickHandler;
8use crate::style::{surface, ColorValue, Variant};
9use crate::theme::{theme, Size};
10
11/// A clickable button. The Mantine `Button`.
12#[derive(IntoElement)]
13pub struct Button {
14    id: ElementId,
15    label: SharedString,
16    variant: Variant,
17    color: ColorValue,
18    size: Size,
19    radius: Option<Size>,
20    full_width: bool,
21    disabled: bool,
22    left_section: Option<gpui::AnyElement>,
23    right_section: Option<gpui::AnyElement>,
24    on_click: Option<ClickHandler>,
25}
26
27impl Button {
28    pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
29        Button {
30            id: id.into(),
31            label: label.into(),
32            variant: Variant::Filled,
33            color: ColorValue::default(),
34            size: Size::Sm,
35            radius: None,
36            full_width: false,
37            disabled: false,
38            left_section: None,
39            right_section: None,
40            on_click: None,
41        }
42    }
43
44    pub fn variant(mut self, variant: Variant) -> Self {
45        self.variant = variant;
46        self
47    }
48
49    /// Override the button color. Accepts a palette `ColorName` or any explicit
50    /// color (e.g. `color!(rgba(34, 139, 230, 1))`).
51    pub fn color(mut self, color: impl Into<ColorValue>) -> Self {
52        self.color = color.into();
53        self
54    }
55
56    pub fn size(mut self, size: Size) -> Self {
57        self.size = size;
58        self
59    }
60
61    pub fn radius(mut self, radius: Size) -> Self {
62        self.radius = Some(radius);
63        self
64    }
65
66    pub fn full_width(mut self, full_width: bool) -> Self {
67        self.full_width = full_width;
68        self
69    }
70
71    pub fn disabled(mut self, disabled: bool) -> Self {
72        self.disabled = disabled;
73        self
74    }
75
76    /// Content shown before the label (e.g. an icon).
77    pub fn left_section(mut self, section: impl IntoElement) -> Self {
78        self.left_section = Some(section.into_any_element());
79        self
80    }
81
82    /// Content shown after the label.
83    pub fn right_section(mut self, section: impl IntoElement) -> Self {
84        self.right_section = Some(section.into_any_element());
85        self
86    }
87
88    pub fn on_click(
89        mut self,
90        handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
91    ) -> Self {
92        self.on_click = Some(Box::new(handler));
93        self
94    }
95
96    /// (height, horizontal padding, font size) for the size scale.
97    fn metrics(&self) -> (f32, f32, f32) {
98        match self.size {
99            Size::Xs => (30.0, 14.0, 12.0),
100            Size::Sm => (36.0, 18.0, 14.0),
101            Size::Md => (42.0, 22.0, 16.0),
102            Size::Lg => (50.0, 26.0, 18.0),
103            Size::Xl => (60.0, 32.0, 20.0),
104        }
105    }
106}
107
108impl RenderOnce for Button {
109    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
110        let t = theme(cx);
111        let s = surface(t, self.color, self.variant);
112        let (height, pad_x, font) = self.metrics();
113        let radius = t.radius(self.radius.unwrap_or(t.default_radius));
114
115        let mut el = div()
116            .id(self.id)
117            .flex()
118            .items_center()
119            .justify_center()
120            .gap(px(8.0))
121            .h(px(height))
122            .px(px(pad_x))
123            .rounded(px(radius))
124            .bg(s.bg)
125            .text_color(s.fg)
126            .text_size(px(font))
127            .font_weight(FontWeight::SEMIBOLD);
128
129        if let Some(border) = s.border {
130            el = el.border_1().border_color(border);
131        }
132        if self.full_width {
133            el = el.w_full();
134        }
135        if let Some(left) = self.left_section {
136            el = el.child(left);
137        }
138        el = el.child(self.label);
139        if let Some(right) = self.right_section {
140            el = el.child(right);
141        }
142
143        if self.disabled {
144            el.opacity(0.6)
145        } else {
146            let hover_bg = s.bg_hover;
147            el = el.hover(move |st| st.bg(hover_bg));
148            if let Some(handler) = self.on_click {
149                el = el.on_click(handler);
150            }
151            el
152        }
153    }
154}