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