1use 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#[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 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 pub fn left_section(mut self, section: impl IntoElement) -> Self {
78 self.left_section = Some(section.into_any_element());
79 self
80 }
81
82 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 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}