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