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 let focus = t.primary().hsla();
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 .tab_index(0)
131 .focus(move |style| style.border_2().border_color(focus))
132 .on_key_down(crate::input::handle_tab);
133
134 if let Some(border) = s.border {
135 el = el.border_1().border_color(border);
136 }
137 if self.full_width {
138 el = el.w_full();
139 }
140 if let Some(left) = self.left_section {
141 el = el.child(left);
142 }
143 el = el.child(self.label);
144 if let Some(right) = self.right_section {
145 el = el.child(right);
146 }
147
148 let element = if self.disabled {
149 el.tab_stop(false).opacity(0.6)
150 } else {
151 let hover_bg = s.bg_hover;
152 el = el.hover(move |st| st.bg(hover_bg));
153 if let Some(handler) = self.on_click {
154 el = el.on_click(handler);
155 }
156 el
157 };
158
159 element
160 .probe("Button")
161 .attr("variant", self.variant.label())
162 .attr("size", self.size.label())
163 .attr_if("disabled", self.disabled)
164 }
165}